refactor(admin): 合并管理后台并移除 apps/agent
- 平台与代理共用 apps/admin,统一登录 manage/auth/login - 按 userType 展示菜单,修复 token 循环跳转 - 删除独立 apps/agent 前端工程 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
12
apps/admin/src/views/HomeEntry.vue
Normal file
12
apps/admin/src/views/HomeEntry.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { useAuthStore } from '../stores/auth';
|
||||
import AdminDashboard from './Dashboard.vue';
|
||||
import AgentDashboard from './agent/Dashboard.vue';
|
||||
|
||||
const auth = useAuthStore();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AdminDashboard v-if="auth.isAdmin" />
|
||||
<AgentDashboard v-else />
|
||||
</template>
|
||||
@@ -1,21 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import api from '../api';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { useAuthStore, type StaffUser } from '../stores/auth';
|
||||
|
||||
const router = useRouter();
|
||||
const form = ref({ username: 'admin', password: 'Admin@123' });
|
||||
const route = useRoute();
|
||||
const auth = useAuthStore();
|
||||
|
||||
const form = ref({ username: '', password: '' });
|
||||
const loading = ref(false);
|
||||
|
||||
async function login() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const { data } = await api.post('/admin/auth/login', form.value);
|
||||
localStorage.setItem('admin_token', data.data.token);
|
||||
router.push('/');
|
||||
const { data } = await api.post('/manage/auth/login', form.value);
|
||||
const payload = data.data as { token: string; user: StaffUser };
|
||||
auth.setSession(payload.token, payload.user);
|
||||
const redirect = (route.query.redirect as string) || '/';
|
||||
router.push(redirect);
|
||||
} catch {
|
||||
ElMessage.error('登录失败');
|
||||
ElMessage.error('登录失败,请检查账号与密码');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
@@ -27,17 +33,24 @@ async function login() {
|
||||
<el-card style="width: 400px">
|
||||
<div style="text-align: center; margin-bottom: 24px">
|
||||
<img src="/logo.png" alt="TheBet365" style="height: 64px; width: auto" />
|
||||
<h2 style="margin-top: 12px; font-size: 16px; font-weight: 500">平台后台登录</h2>
|
||||
<h2 style="margin-top: 12px; font-size: 16px; font-weight: 500">管理后台登录</h2>
|
||||
<p style="margin-top: 8px; font-size: 12px; color: #888">
|
||||
管理员与代理使用同一入口,系统将根据账号类型进入对应后台
|
||||
</p>
|
||||
</div>
|
||||
<el-form @submit.prevent="login">
|
||||
<el-form-item label="用户名">
|
||||
<el-input v-model="form.username" />
|
||||
<el-input v-model="form.username" autocomplete="username" />
|
||||
</el-form-item>
|
||||
<el-form-item label="密码">
|
||||
<el-input v-model="form.password" type="password" />
|
||||
<el-input v-model="form.password" type="password" autocomplete="current-password" />
|
||||
</el-form-item>
|
||||
<el-button type="primary" native-type="submit" :loading="loading" style="width: 100%">登录</el-button>
|
||||
</el-form>
|
||||
<p style="margin-top: 16px; font-size: 12px; color: #999; line-height: 1.6">
|
||||
演示账号:admin / Admin@123(平台)<br />
|
||||
agent1 / Agent@123(一级代理)
|
||||
</p>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
25
apps/admin/src/views/agent/Bets.vue
Normal file
25
apps/admin/src/views/agent/Bets.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import api from '../../api';
|
||||
|
||||
const bets = ref<unknown[]>([]);
|
||||
|
||||
onMounted(async () => {
|
||||
const { data } = await api.get('/agent/bets');
|
||||
bets.value = data.data.items;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h2>下级注单</h2>
|
||||
<el-table :data="bets">
|
||||
<el-table-column prop="betNo" label="注单号" />
|
||||
<el-table-column label="玩家">
|
||||
<template #default="{ row }">
|
||||
{{ (row as { user?: { username: string } }).user?.username }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="stake" label="投注额" />
|
||||
<el-table-column prop="status" label="状态" />
|
||||
</el-table>
|
||||
</template>
|
||||
35
apps/admin/src/views/agent/Dashboard.vue
Normal file
35
apps/admin/src/views/agent/Dashboard.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import api from '../../api';
|
||||
|
||||
const summary = ref<Record<string, unknown>>({});
|
||||
|
||||
onMounted(async () => {
|
||||
const { data } = await api.get('/agent/reports/summary');
|
||||
summary.value = data.data;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h2>代理概览</h2>
|
||||
<el-row :gutter="16" style="margin-top: 16px">
|
||||
<el-col :span="6">
|
||||
<el-statistic
|
||||
title="授信额度"
|
||||
:value="Number((summary.profile as { creditLimit?: string })?.creditLimit) || 0"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-statistic
|
||||
title="已用额度"
|
||||
:value="Number((summary.profile as { usedCredit?: string })?.usedCredit) || 0"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-statistic title="直属玩家" :value="(summary.directPlayerCount as number) || 0" />
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-statistic title="今日投注" :value="Number(summary.todayStake) || 0" :precision="2" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
68
apps/admin/src/views/agent/Players.vue
Normal file
68
apps/admin/src/views/agent/Players.vue
Normal file
@@ -0,0 +1,68 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import api from '../../api';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
const players = ref<unknown[]>([]);
|
||||
const form = ref({ username: '', password: 'Player@123' });
|
||||
const depositForm = ref({ playerId: '', amount: 100, requestId: '' });
|
||||
|
||||
onMounted(load);
|
||||
|
||||
async function load() {
|
||||
const { data } = await api.get('/agent/players');
|
||||
players.value = data.data;
|
||||
}
|
||||
|
||||
async function create() {
|
||||
await api.post('/agent/players', form.value);
|
||||
ElMessage.success('玩家已创建');
|
||||
load();
|
||||
}
|
||||
|
||||
async function deposit() {
|
||||
depositForm.value.requestId = `dep-${Date.now()}`;
|
||||
await api.post(`/agent/players/${depositForm.value.playerId}/deposit`, {
|
||||
amount: depositForm.value.amount,
|
||||
requestId: depositForm.value.requestId,
|
||||
});
|
||||
ElMessage.success('上分成功');
|
||||
load();
|
||||
}
|
||||
|
||||
async function withdraw(playerId: string, amount: number) {
|
||||
await api.post(`/agent/players/${playerId}/withdraw`, {
|
||||
amount,
|
||||
requestId: `wd-${Date.now()}`,
|
||||
});
|
||||
ElMessage.success('下分成功');
|
||||
load();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h2>直属玩家</h2>
|
||||
<el-form inline style="margin-bottom: 16px">
|
||||
<el-input v-model="form.username" placeholder="用户名" />
|
||||
<el-button type="primary" @click="create">创建玩家</el-button>
|
||||
</el-form>
|
||||
<el-form inline style="margin-bottom: 16px">
|
||||
<el-input v-model="depositForm.playerId" placeholder="玩家ID" style="width: 100px" />
|
||||
<el-input-number v-model="depositForm.amount" :min="1" />
|
||||
<el-button type="success" @click="deposit">上分</el-button>
|
||||
</el-form>
|
||||
<el-table :data="players">
|
||||
<el-table-column prop="id" label="ID" />
|
||||
<el-table-column prop="username" label="用户名" />
|
||||
<el-table-column label="余额">
|
||||
<template #default="{ row }">
|
||||
{{ (row as { wallet?: { availableBalance: string } }).wallet?.availableBalance }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" @click="withdraw((row as { id: string }).id, 50)">下分50</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
39
apps/admin/src/views/agent/SubAgents.vue
Normal file
39
apps/admin/src/views/agent/SubAgents.vue
Normal file
@@ -0,0 +1,39 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import api from '../../api';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
const agents = ref<unknown[]>([]);
|
||||
const form = ref({ username: '', password: 'Agent@123', creditLimit: 10000 });
|
||||
|
||||
onMounted(load);
|
||||
|
||||
async function load() {
|
||||
const { data } = await api.get('/agent/agents');
|
||||
agents.value = data.data;
|
||||
}
|
||||
|
||||
async function create() {
|
||||
await api.post('/agent/agents', form.value);
|
||||
ElMessage.success('下级代理已创建');
|
||||
load();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h2>下级代理(仅一级代理可见)</h2>
|
||||
<el-form inline style="margin-bottom: 16px">
|
||||
<el-input v-model="form.username" placeholder="用户名" />
|
||||
<el-input-number v-model="form.creditLimit" />
|
||||
<el-button type="primary" @click="create">创建二级代理</el-button>
|
||||
</el-form>
|
||||
<el-table :data="agents">
|
||||
<el-table-column label="用户名">
|
||||
<template #default="{ row }">
|
||||
{{ (row as { user?: { username: string } }).user?.username }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="creditLimit" label="额度" />
|
||||
<el-table-column prop="usedCredit" label="已用" />
|
||||
</el-table>
|
||||
</template>
|
||||
Reference in New Issue
Block a user