feat: 管理端 RBAC 权限体系与员工管理

新增多角色权限控制(赛事/财务/客服管理员),支持员工 CRUD、路由菜单按权限显隐、审计日志范围过滤;登录返回角色与权限列表。玩家端赛事列表增加静默刷新避免图片闪烁。Seed 补充演示员工账号与充值相关权限。附带 RBAC/审计范围单元测试及 UAT 文档更新。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-15 17:52:39 +08:00
parent be5b4a4921
commit 567ec9ec8a
44 changed files with 1717 additions and 125 deletions

View File

@@ -1,17 +1,40 @@
<script setup lang="ts">
import { shallowRef, onBeforeMount, type Component } from 'vue';
import { RouterView } from 'vue-router';
import { RouterView, RouterLink, useRoute, useRouter } from 'vue-router';
import { useAdminLocale } from '../composables/useAdminLocale';
import { useAuthStore } from '../stores/auth';
import { ensureStaffSession } from '../utils/session-hydrate';
import DashboardSubNav from '../components/DashboardSubNav.vue';
import { usePermissions } from '../composables/usePermissions';
import { AdminPerm } from '../constants/permissions';
const auth = useAuthStore();
const route = useRoute();
const router = useRouter();
const { t } = useAdminLocale();
const { hasPermission, role } = usePermissions();
const agentDashboard = shallowRef<Component | null>(null);
const booting = shallowRef(true);
const showSupportShortcuts = shallowRef(false);
onBeforeMount(async () => {
await ensureStaffSession();
if (!auth.isAdmin.value) {
if (auth.isAdmin.value) {
const code = role.value;
if (code === 'FINANCE_ADMIN' && route.path === '/') {
await router.replace('/dashboard/players');
} else if (code === 'SUPPORT') {
showSupportShortcuts.value = true;
if (route.path === '/' || route.path === '/dashboard/players') {
if (hasPermission(AdminPerm.usersView, AdminPerm.agentsView)) {
await router.replace('/users');
} else if (hasPermission(AdminPerm.bets)) {
await router.replace('/bets');
}
}
}
} else {
agentDashboard.value = (await import('./agent/Dashboard.vue')).default;
}
booting.value = false;
@@ -21,6 +44,17 @@ onBeforeMount(async () => {
<template>
<div v-if="booting" v-loading="true" class="home-boot" />
<template v-else-if="auth.isAdmin.value">
<div v-if="showSupportShortcuts" class="support-shortcuts">
<RouterLink v-if="hasPermission(AdminPerm.usersView, AdminPerm.agentsView)" to="/users" class="support-link">
{{ t('nav.agents_players') }}
</RouterLink>
<RouterLink v-if="hasPermission(AdminPerm.bets)" to="/bets" class="support-link">
{{ t('nav.bets') }}
</RouterLink>
<RouterLink v-if="hasPermission(AdminPerm.reports)" to="/finance-logs" class="support-link">
{{ t('nav.finance_logs') }}
</RouterLink>
</div>
<div class="dashboard-shell">
<div class="list-chrome">
<div class="list-chrome__row">
@@ -40,6 +74,28 @@ onBeforeMount(async () => {
min-height: 240px;
}
.support-shortcuts {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 12px;
}
.support-link {
padding: 8px 14px;
border-radius: 8px;
border: 1px solid var(--border);
background: #fff;
font-size: 13px;
font-weight: 650;
color: var(--text);
}
.support-link:hover {
border-color: var(--primary);
color: var(--primary);
}
.dashboard-shell :deep(.list-chrome) {
margin-bottom: 4px;
}