统一角色体系为admin/agent/user,修复代理查询角色错误,添加语言切换和明暗切换

This commit is contained in:
Admin
2026-04-29 17:33:54 +08:00
parent 5c63882e71
commit 2a8f32cbb9
21 changed files with 78 additions and 196 deletions
@@ -7,10 +7,10 @@ import TeamSwitcher from '@/components/app-sidebar/team-switcher.vue'
import NavFooter from '@/components/developer-sidebar/nav-footer.vue'
const user = reactive({
name: '开发者',
email: 'developer@example.com',
avatar: '/avatars/developer.jpg',
role: 'developer',
name: '管理员',
email: 'admin@example.com',
avatar: '/avatars/admin.jpg',
role: 'admin',
})
onMounted(() => {
@@ -228,7 +228,7 @@ watch(route, () => {
{{ t('nav.pricing') }}
</router-link>
<template v-if="isLoggedIn && currentUser?.role === 'developer'">
<template v-if="isLoggedIn && currentUser?.role === 'admin'">
<div class="w-px h-4 bg-border mx-1" />
<UiDropdownMenu>
@@ -300,79 +300,6 @@ watch(route, () => {
</UiDropdownMenuContent>
</UiDropdownMenu>
</template>
<template v-if="isLoggedIn && currentUser?.role === 'admin'">
<div class="w-px h-4 bg-border mx-1" />
<UiDropdownMenu>
<UiDropdownMenuTrigger as-child>
<button
class="transition-colors hover:text-foreground/80 px-3 py-2 rounded-lg flex items-center gap-1"
:class="isMenuActive(adminMenus.main.items) ? 'text-foreground bg-accent' : 'text-foreground/60'"
>
{{ adminMenus.main.title }}
<Icon icon="lucide:chevron-down" class="h-4 w-4" />
</button>
</UiDropdownMenuTrigger>
<UiDropdownMenuContent align="start" class="w-40">
<UiDropdownMenuItem
v-for="item in adminMenus.main.items"
:key="item.url"
:class="route.path === item.url ? 'bg-accent' : ''"
@click="router.push(item.url)"
>
<Icon :icon="item.icon" class="mr-2 h-4 w-4" />
{{ item.title }}
</UiDropdownMenuItem>
</UiDropdownMenuContent>
</UiDropdownMenu>
<UiDropdownMenu>
<UiDropdownMenuTrigger as-child>
<button
class="transition-colors hover:text-foreground/80 px-3 py-2 rounded-lg flex items-center gap-1"
:class="isMenuActive(adminMenus.content.items) ? 'text-foreground bg-accent' : 'text-foreground/60'"
>
{{ adminMenus.content.title }}
<Icon icon="lucide:chevron-down" class="h-4 w-4" />
</button>
</UiDropdownMenuTrigger>
<UiDropdownMenuContent align="start" class="w-40">
<UiDropdownMenuItem
v-for="item in adminMenus.content.items"
:key="item.url"
:class="route.path === item.url ? 'bg-accent' : ''"
@click="router.push(item.url)"
>
<Icon :icon="item.icon" class="mr-2 h-4 w-4" />
{{ item.title }}
</UiDropdownMenuItem>
</UiDropdownMenuContent>
</UiDropdownMenu>
<UiDropdownMenu>
<UiDropdownMenuTrigger as-child>
<button
class="transition-colors hover:text-foreground/80 px-3 py-2 rounded-lg flex items-center gap-1"
:class="isMenuActive(adminMenus.system.items) ? 'text-foreground bg-accent' : 'text-foreground/60'"
>
{{ adminMenus.system.title }}
<Icon icon="lucide:chevron-down" class="h-4 w-4" />
</button>
</UiDropdownMenuTrigger>
<UiDropdownMenuContent align="start" class="w-40">
<UiDropdownMenuItem
v-for="item in adminMenus.system.items"
:key="item.url"
:class="route.path === item.url ? 'bg-accent' : ''"
@click="router.push(item.url)"
>
<Icon :icon="item.icon" class="mr-2 h-4 w-4" />
{{ item.title }}
</UiDropdownMenuItem>
</UiDropdownMenuContent>
</UiDropdownMenu>
</template>
</nav>
</div>
@@ -438,18 +365,14 @@ watch(route, () => {
<Icon icon="lucide:user" class="mr-2 h-4 w-4" />
<span>{{ t('nav.profile') }}</span>
</UiDropdownMenuItem>
<UiDropdownMenuItem v-if="currentUser?.role === 'developer'" @click="router.push('/developer')">
<UiDropdownMenuItem v-if="currentUser?.role === 'admin'" @click="router.push('/developer')">
<Icon icon="lucide:layout-dashboard" class="mr-2 h-4 w-4" />
<span>{{ t('nav.developerDashboard') }}</span>
</UiDropdownMenuItem>
<UiDropdownMenuItem v-if="currentUser?.role === 'agent'" @click="router.push('/agent/dashboard')">
<UiDropdownMenuItem v-if="currentUser?.role === 'agent'" @click="router.push('/agent')">
<Icon icon="lucide:layout-dashboard" class="mr-2 h-4 w-4" />
<span>{{ t('nav.agentDashboard') }}</span>
</UiDropdownMenuItem>
<UiDropdownMenuItem v-if="currentUser?.role === 'admin'" @click="router.push('/admin')">
<Icon icon="lucide:shield" class="mr-2 h-4 w-4" />
<span>{{ t('nav.adminDashboard') }}</span>
</UiDropdownMenuItem>
<UiDropdownMenuSeparator />
<UiDropdownMenuItem @click="handleLogout">
<Icon icon="lucide:log-out" class="mr-2 h-4 w-4" />
@@ -502,7 +425,7 @@ watch(route, () => {
</button>
</div>
<template v-if="isLoggedIn && currentUser?.role === 'developer'">
<template v-if="isLoggedIn && currentUser?.role === 'admin'">
<div class="space-y-1">
<p class="text-xs font-medium text-muted-foreground px-3 mb-2">
{{ developerMenus.main.title }}
@@ -551,56 +474,6 @@ watch(route, () => {
</button>
</div>
</template>
<template v-if="isLoggedIn && currentUser?.role === 'admin'">
<div class="space-y-1">
<p class="text-xs font-medium text-muted-foreground px-3 mb-2">
{{ adminMenus.main.title }}
</p>
<button
v-for="item in adminMenus.main.items"
:key="item.url"
class="w-full flex items-center gap-3 px-3 py-2 text-sm rounded-lg transition-colors"
:class="isActive(item.url) ? 'bg-accent text-foreground' : 'text-muted-foreground hover:bg-accent hover:text-foreground'"
@click="navigateTo(item.url)"
>
<Icon :icon="item.icon" class="size-4" />
{{ item.title }}
</button>
</div>
<div class="space-y-1">
<p class="text-xs font-medium text-muted-foreground px-3 mb-2">
{{ adminMenus.content.title }}
</p>
<button
v-for="item in adminMenus.content.items"
:key="item.url"
class="w-full flex items-center gap-3 px-3 py-2 text-sm rounded-lg transition-colors"
:class="isActive(item.url) ? 'bg-accent text-foreground' : 'text-muted-foreground hover:bg-accent hover:text-foreground'"
@click="navigateTo(item.url)"
>
<Icon :icon="item.icon" class="size-4" />
{{ item.title }}
</button>
</div>
<div class="space-y-1">
<p class="text-xs font-medium text-muted-foreground px-3 mb-2">
{{ adminMenus.system.title }}
</p>
<button
v-for="item in adminMenus.system.items"
:key="item.url"
class="w-full flex items-center gap-3 px-3 py-2 text-sm rounded-lg transition-colors"
:class="isActive(item.url) ? 'bg-accent text-foreground' : 'text-muted-foreground hover:bg-accent hover:text-foreground'"
@click="navigateTo(item.url)"
>
<Icon :icon="item.icon" class="size-4" />
{{ item.title }}
</button>
</div>
</template>
</nav>
<div v-if="!isLoggedIn" class="pt-4 border-t mt-4 space-y-2">
+5 -1
View File
@@ -6,7 +6,9 @@ import { useRouter } from 'vue-router'
import NavTeam from '@/components/app-sidebar/nav-team.vue'
import TeamSwitcher from '@/components/app-sidebar/team-switcher.vue'
import LanguageChange from '@/components/language-change.vue'
import ThemePopover from '@/components/custom-theme/theme-popover.vue'
import ToggleTheme from '@/components/toggle-theme.vue'
const router = useRouter()
@@ -218,7 +220,9 @@ function handleLogout() {
</template>
</UiBreadcrumbList>
</UiBreadcrumb>
<div class="ml-auto">
<div class="ml-auto flex items-center gap-2">
<LanguageChange />
<ToggleTheme />
<ThemePopover />
</div>
</header>
+4
View File
@@ -3,7 +3,9 @@ import { computed } from 'vue'
import { useRouter } from 'vue-router'
import DeveloperSidebar from '@/components/developer-sidebar/index.vue'
import LanguageChange from '@/components/language-change.vue'
import ThemePopover from '@/components/custom-theme/theme-popover.vue'
import ToggleTheme from '@/components/toggle-theme.vue'
const router = useRouter()
@@ -91,6 +93,8 @@ const breadcrumbs = computed(() => {
</UiBreadcrumb>
<div class="ml-auto flex items-center gap-2">
<LanguageChange />
<ToggleTheme />
<ThemePopover />
</div>
</header>
@@ -30,7 +30,7 @@ const form = ref({
data_type: 'single',
max_records: 0,
scope: 'app',
write_permission: 'developer',
write_permission: 'admin',
description: '',
status: 'active',
})
@@ -61,7 +61,7 @@ async function fetchVariable() {
data_type: variable.data_type || 'single',
max_records: variable.max_records || 0,
scope: variable.scope || 'app',
write_permission: variable.write_permission || 'developer',
write_permission: variable.write_permission || 'admin',
description: variable.description || '',
status: variable.status || 'active',
}
@@ -297,10 +297,10 @@ onMounted(async () => {
<UiRadioGroup v-model="form.write_permission" class="grid grid-cols-1 gap-4">
<div
class="flex items-start gap-3 p-4 rounded-lg border cursor-pointer transition-colors"
:class="form.write_permission === 'developer' ? 'border-primary bg-primary/5' : 'hover:bg-accent'"
@click="form.write_permission = 'developer'"
:class="form.write_permission === 'admin' ? 'border-primary bg-primary/5' : 'hover:bg-accent'"
@click="form.write_permission = 'admin'"
>
<UiRadioGroupItem value="developer" class="mt-0.5" />
<UiRadioGroupItem value="admin" class="mt-0.5" />
<div>
<div class="flex items-center gap-2">
<Icon icon="lucide:lock" class="size-4 text-primary" />
@@ -398,7 +398,7 @@ onMounted(async () => {
<div class="flex justify-between text-sm">
<span class="text-muted-foreground">{{ t('developer.cloudVariables.create.writePermission') }}</span>
<UiBadge variant="secondary">
{{ form.write_permission === 'developer' ? t('developer.cloudVariables.create.developerOnly') : form.write_permission === 'app_user' ? '应用用户可写' : t('developer.cloudVariables.create.userWritable') }}
{{ form.write_permission === 'admin' ? t('developer.cloudVariables.create.developerOnly') : form.write_permission === 'app_user' ? '应用用户可写' : t('developer.cloudVariables.create.userWritable') }}
</UiBadge>
</div>
<div class="flex justify-between text-sm items-center">
@@ -30,7 +30,7 @@ const form = ref({
data_type: 'single',
max_records: 0,
scope: 'app',
write_permission: 'developer',
write_permission: 'admin',
description: '',
status: 'active',
})
@@ -391,10 +391,10 @@ onMounted(() => {
<UiRadioGroup v-model="form.write_permission" class="grid grid-cols-1 gap-4">
<div
class="flex items-start gap-3 p-4 rounded-lg border cursor-pointer transition-colors"
:class="form.write_permission === 'developer' ? 'border-primary bg-primary/5' : 'hover:bg-accent'"
@click="form.write_permission = 'developer'"
:class="form.write_permission === 'admin' ? 'border-primary bg-primary/5' : 'hover:bg-accent'"
@click="form.write_permission = 'admin'"
>
<UiRadioGroupItem value="developer" class="mt-0.5" />
<UiRadioGroupItem value="admin" class="mt-0.5" />
<div>
<div class="flex items-center gap-2">
<Icon icon="lucide:lock" class="size-4 text-primary" />
@@ -492,7 +492,7 @@ onMounted(() => {
<div class="flex justify-between text-sm">
<span class="text-muted-foreground">{{ t('developer.cloudVariables.create.writePermission') }}</span>
<UiBadge variant="secondary">
{{ form.write_permission === 'developer' ? t('developer.cloudVariables.create.developerOnly') : form.write_permission === 'app_user' ? '应用用户可写' : t('developer.cloudVariables.create.userWritable') }}
{{ form.write_permission === 'admin' ? t('developer.cloudVariables.create.developerOnly') : form.write_permission === 'app_user' ? '应用用户可写' : t('developer.cloudVariables.create.userWritable') }}
</UiBadge>
</div>
<div class="flex justify-between text-sm items-center">
+1 -3
View File
@@ -38,9 +38,7 @@ async function handleLogin() {
toast.success('登录成功')
const isAgent = data.user.role === 'agent' || data.user.parent_agent_id
if (isAgent) {
if (data.user.role === 'agent' || data.user.parent_agent_id) {
router.push('/agent')
}
else {
@@ -28,7 +28,7 @@ const form = ref({
default_value: '',
var_type: 'string',
scope: 'app',
write_permission: 'developer',
write_permission: 'admin',
description: '',
status: 'active',
})
@@ -57,7 +57,7 @@ async function fetchVariable() {
default_value: variable.default_value || '',
var_type: variable.var_type || 'string',
scope: variable.scope || 'app',
write_permission: variable.write_permission || 'developer',
write_permission: variable.write_permission || 'admin',
description: variable.description || '',
status: variable.status || 'active',
}
@@ -239,10 +239,10 @@ onMounted(async () => {
<UiRadioGroup v-model="form.write_permission" class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div
class="flex items-start gap-3 p-4 rounded-lg border cursor-pointer transition-colors"
:class="form.write_permission === 'developer' ? 'border-primary bg-primary/5' : 'hover:bg-accent'"
@click="form.write_permission = 'developer'"
:class="form.write_permission === 'admin' ? 'border-primary bg-primary/5' : 'hover:bg-accent'"
@click="form.write_permission = 'admin'"
>
<UiRadioGroupItem value="developer" class="mt-0.5" />
<UiRadioGroupItem value="admin" class="mt-0.5" />
<div>
<div class="flex items-center gap-2">
<Icon icon="lucide:lock" class="size-4 text-primary" />
@@ -310,7 +310,7 @@ onMounted(async () => {
<div class="flex justify-between text-sm">
<span class="text-muted-foreground">{{ t('developer.cloudVariables.create.writePermission') }}</span>
<UiBadge variant="secondary">
{{ form.write_permission === 'developer' ? t('developer.cloudVariables.create.developerOnly') : t('developer.cloudVariables.create.userWritable') }}
{{ form.write_permission === 'admin' ? t('developer.cloudVariables.create.developerOnly') : t('developer.cloudVariables.create.userWritable') }}
</UiBadge>
</div>
<div class="flex justify-between text-sm items-center">
@@ -30,7 +30,7 @@ const form = ref({
data_type: 'single',
max_records: 0,
scope: 'app',
write_permission: 'developer',
write_permission: 'admin',
description: '',
status: 'active',
})
@@ -391,10 +391,10 @@ onMounted(() => {
<UiRadioGroup v-model="form.write_permission" class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div
class="flex items-start gap-3 p-4 rounded-lg border cursor-pointer transition-colors"
:class="form.write_permission === 'developer' ? 'border-primary bg-primary/5' : 'hover:bg-accent'"
@click="form.write_permission = 'developer'"
:class="form.write_permission === 'admin' ? 'border-primary bg-primary/5' : 'hover:bg-accent'"
@click="form.write_permission = 'admin'"
>
<UiRadioGroupItem value="developer" class="mt-0.5" />
<UiRadioGroupItem value="admin" class="mt-0.5" />
<div>
<div class="flex items-center gap-2">
<Icon icon="lucide:lock" class="size-4 text-primary" />
@@ -462,7 +462,7 @@ onMounted(() => {
<div class="flex justify-between text-sm">
<span class="text-muted-foreground">{{ t('developer.cloudVariables.create.writePermission') }}</span>
<UiBadge variant="secondary">
{{ form.write_permission === 'developer' ? t('developer.cloudVariables.create.developerOnly') : t('developer.cloudVariables.create.userWritable') }}
{{ form.write_permission === 'admin' ? t('developer.cloudVariables.create.developerOnly') : t('developer.cloudVariables.create.userWritable') }}
</UiBadge>
</div>
<div class="flex justify-between text-sm items-center">
+1 -2
View File
@@ -273,9 +273,8 @@ function formatNumber(num: number) {
function getRoleName(role: string) {
const roles: Record<string, string> = {
developer: '开发者',
agent: '代理商',
admin: '管理员',
agent: '代理商',
}
return roles[role] || role
}
+5 -1
View File
@@ -19,10 +19,14 @@ export function createRouterGuard(router: Router) {
const isAuthPage = to.path === '/login' || to.path === '/register'
if (requiresAuth && !token) {
return '/login'
return { path: '/login', query: { redirect: to.fullPath } }
}
if (isAuthPage && token && user) {
const redirect = (to.query.redirect as string) || ''
if (redirect) {
return redirect
}
if (user.role === 'agent' || user.parent_agent_id) {
return '/agent'
}
+1 -1
View File
@@ -26,7 +26,7 @@ const routes: RouteRecordRaw[] = [
{
path: '/developer',
component: () => import('@/layouts/developer.vue'),
meta: { auth: true, role: 'developer' },
meta: { auth: true, role: 'admin' },
children: [
{
path: '',