feat: 管理端 RBAC 权限体系与员工管理
新增多角色权限控制(赛事/财务/客服管理员),支持员工 CRUD、路由菜单按权限显隐、审计日志范围过滤;登录返回角色与权限列表。玩家端赛事列表增加静默刷新避免图片闪烁。Seed 补充演示员工账号与充值相关权限。附带 RBAC/审计范围单元测试及 UAT 文档更新。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -4,6 +4,8 @@ import { RouterView, RouterLink, useRoute, useRouter } from 'vue-router';
|
||||
import { useAuthStore } from '../stores/auth';
|
||||
import { useAdminLocale } from '../composables/useAdminLocale';
|
||||
import { useSmokeTestsAllowed } from '../composables/useSmokeTestsAllowed';
|
||||
import { usePermissions } from '../composables/usePermissions';
|
||||
import { AdminPerm } from '../constants/permissions';
|
||||
import AdminLocaleSwitcher from '../components/AdminLocaleSwitcher.vue';
|
||||
import AdminNavIcon from '../components/AdminNavIcon.vue';
|
||||
import { resolveAdminBreadcrumb } from '../utils/admin-breadcrumb';
|
||||
@@ -13,28 +15,44 @@ const router = useRouter();
|
||||
const auth = useAuthStore();
|
||||
const { t } = useAdminLocale();
|
||||
const { allowed: smokeTestsAllowed, ensureLoaded: ensureSmokeTestsAllowed } = useSmokeTestsAllowed();
|
||||
const { hasPermission, role: adminRole } = usePermissions();
|
||||
|
||||
const sidebarOpen = ref(false);
|
||||
const isMobileNav = ref(false);
|
||||
|
||||
type AdminMenuItem = {
|
||||
path: string;
|
||||
label: string;
|
||||
icon: string;
|
||||
matchPrefix?: boolean;
|
||||
permissions: string[];
|
||||
/** Hide for these admin role codes (SUPER_ADMIN is never excluded). */
|
||||
excludeRoles?: string[];
|
||||
};
|
||||
|
||||
function menuVisible(item: AdminMenuItem): boolean {
|
||||
const code = adminRole.value;
|
||||
if (item.path === '/smoke-tests' && smokeTestsAllowed.value === false) return false;
|
||||
if (code && code !== 'SUPER_ADMIN' && item.excludeRoles?.includes(code)) return false;
|
||||
return hasPermission(...item.permissions);
|
||||
}
|
||||
|
||||
const adminMenus = computed(() => {
|
||||
const items = [
|
||||
{ path: '/', label: t('nav.dashboard'), icon: 'dashboard', matchPrefix: true },
|
||||
{ path: '/matches', label: t('nav.matches'), icon: 'matches', matchPrefix: true },
|
||||
{ path: '/users', label: t('nav.agents_players'), icon: 'users' },
|
||||
{ path: '/finance-logs', label: t('nav.finance_logs'), icon: 'finance' },
|
||||
{ path: '/deposit', label: t('nav.deposit_manage'), icon: 'deposit', matchPrefix: true },
|
||||
{ path: '/cashback', label: t('nav.cashback'), icon: 'cashback' },
|
||||
{ path: '/bets', label: t('nav.bets'), icon: 'bets' },
|
||||
{ path: '/contents', label: t('nav.contents'), icon: 'contents' },
|
||||
{ path: '/media', label: t('nav.media'), icon: 'media' },
|
||||
{ path: '/audit', label: t('nav.audit'), icon: 'audit' },
|
||||
{ path: '/smoke-tests', label: t('nav.smoke_tests'), icon: 'smoke-tests' },
|
||||
const items: AdminMenuItem[] = [
|
||||
{ path: '/', label: t('nav.dashboard'), icon: 'dashboard', matchPrefix: true, permissions: [AdminPerm.reports], excludeRoles: ['SUPPORT'] },
|
||||
{ path: '/matches', label: t('nav.matches'), icon: 'matches', matchPrefix: true, permissions: [AdminPerm.matches] },
|
||||
{ path: '/users', label: t('nav.agents_players'), icon: 'users', permissions: [AdminPerm.usersView, AdminPerm.agentsView] },
|
||||
{ path: '/finance-logs', label: t('nav.finance_logs'), icon: 'finance', permissions: [AdminPerm.reports], excludeRoles: ['MATCH_ADMIN'] },
|
||||
{ path: '/deposit', label: t('nav.deposit_manage'), icon: 'deposit', matchPrefix: true, permissions: [AdminPerm.depositManage, AdminPerm.depositReview] },
|
||||
{ path: '/cashback', label: t('nav.cashback'), icon: 'cashback', permissions: [AdminPerm.cashback], excludeRoles: ['MATCH_ADMIN', 'SUPPORT'] },
|
||||
{ path: '/bets', label: t('nav.bets'), icon: 'bets', permissions: [AdminPerm.bets] },
|
||||
{ path: '/contents', label: t('nav.contents'), icon: 'contents', permissions: [AdminPerm.content] },
|
||||
{ path: '/media', label: t('nav.media'), icon: 'media', permissions: [AdminPerm.content, AdminPerm.matches] },
|
||||
{ path: '/audit', label: t('nav.audit'), icon: 'audit', permissions: [AdminPerm.audit] },
|
||||
{ path: '/staff', label: t('nav.staff'), icon: 'users', permissions: [AdminPerm.settings] },
|
||||
{ path: '/smoke-tests', label: t('nav.smoke_tests'), icon: 'smoke-tests', permissions: [AdminPerm.settings] },
|
||||
];
|
||||
if (smokeTestsAllowed.value === false) {
|
||||
return items.filter((item) => item.path !== '/smoke-tests');
|
||||
}
|
||||
return items;
|
||||
return items.filter(menuVisible);
|
||||
});
|
||||
|
||||
const agentMenus = computed(() => [
|
||||
@@ -78,7 +96,14 @@ const currentLabel = computed(() => {
|
||||
const topbarCrumbs = computed(() => resolveAdminBreadcrumb(route.path, t));
|
||||
|
||||
const roleLabel = computed(() => {
|
||||
if (auth.isAdmin.value) return t('role.admin');
|
||||
if (auth.isAdmin.value) {
|
||||
const code = adminRole.value;
|
||||
if (code === 'MATCH_ADMIN') return t('role.match_admin');
|
||||
if (code === 'FINANCE_ADMIN') return t('role.finance_admin');
|
||||
if (code === 'SUPPORT') return t('role.support');
|
||||
if (code === 'SUPER_ADMIN') return t('role.super_admin');
|
||||
return t('role.admin');
|
||||
}
|
||||
const level = auth.user.value?.agentLevel;
|
||||
if (auth.isAgent.value && level != null && level > 0) {
|
||||
return t('role.agent_level', { n: level });
|
||||
|
||||
Reference in New Issue
Block a user