mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
feat(ui): land viewers on /chat and show real role in switcher
This commit is contained in:
parent
777e2ecee2
commit
ccd86dbb80
@ -14,6 +14,7 @@
|
||||
</span>
|
||||
<template v-if="!collapsed">
|
||||
<span class="ws-trigger__name">{{ currentLabel }}</span>
|
||||
<span v-if="roleBadge" class="ws-trigger__role" :title="roleBadge.tooltip">{{ roleBadge.label }}</span>
|
||||
<svg class="ws-trigger__arrow" :class="{ open }" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>
|
||||
</template>
|
||||
</button>
|
||||
@ -103,6 +104,25 @@ const workspaces = computed(() => store.workspaces)
|
||||
const currentWorkspaceId = computed(() => store.currentWorkspaceId)
|
||||
const currentLabel = computed(() => store.currentWorkspace?.name || 'Workspace')
|
||||
|
||||
// Show the REAL memberRole — never effective — so a global admin viewing a
|
||||
// workspace they have not joined sees "Global admin (non-member)" instead of
|
||||
// being silently labelled as owner. See WorkspaceWithRoleVO docs.
|
||||
const roleBadge = computed(() => {
|
||||
const ws = store.currentWorkspace
|
||||
if (!ws) return null
|
||||
const real = ws.memberRole as string | null | undefined
|
||||
if (ws.isGlobalAdmin) {
|
||||
return {
|
||||
label: t('nav.roleAdmin'),
|
||||
tooltip: real
|
||||
? `${t('nav.roleAdmin')} (${real})`
|
||||
: `${t('nav.roleAdmin')} (non-member)`,
|
||||
}
|
||||
}
|
||||
if (!real) return null
|
||||
return { label: real, tooltip: real }
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
store.fetchWorkspaces()
|
||||
})
|
||||
@ -178,6 +198,18 @@ function onManage() {
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
.ws-trigger__role {
|
||||
flex-shrink: 0;
|
||||
padding: 1px 6px;
|
||||
border-radius: 8px;
|
||||
background: var(--mc-primary-bg, rgba(217, 109, 70, 0.12));
|
||||
color: var(--mc-primary, #d96d46);
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.ws-trigger__arrow {
|
||||
flex-shrink: 0;
|
||||
color: var(--mc-sidebar-text, var(--mc-text-tertiary));
|
||||
|
||||
@ -61,9 +61,11 @@ import { reactive, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { authApi } from '@/api/index'
|
||||
import { useWorkspaceStore } from '@/stores/useWorkspaceStore'
|
||||
|
||||
const router = useRouter()
|
||||
const { t } = useI18n()
|
||||
const workspaceStore = useWorkspaceStore()
|
||||
const loading = ref(false)
|
||||
const showPassword = ref(false)
|
||||
const errorMsg = ref('')
|
||||
@ -80,7 +82,15 @@ async function handleLogin() {
|
||||
localStorage.setItem('userId', String(data.id || '1'))
|
||||
localStorage.setItem('username', data.username || form.username)
|
||||
localStorage.setItem('role', data.role || 'user')
|
||||
router.push('/')
|
||||
// Resolve capabilities before deciding the landing route so a viewer
|
||||
// lands on /chat (their only capability) and member+ on /dashboard.
|
||||
try {
|
||||
await workspaceStore.fetchWorkspaces()
|
||||
} catch {
|
||||
/* default-deny is fine; router guard will still steer */
|
||||
}
|
||||
const target = workspaceStore.can('view:dashboard') ? '/dashboard' : '/chat'
|
||||
router.push(target)
|
||||
} catch (e: any) {
|
||||
errorMsg.value = typeof e === 'string' ? e : t('login.failed')
|
||||
} finally {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user