优化
This commit is contained in:
@@ -7,9 +7,6 @@ import { ElMessageBox } from 'element-plus'
|
||||
import { useAuthStore } from '@/core/auth/auth'
|
||||
import AppLayout from './AppLayout.vue'
|
||||
|
||||
const { getUnreadCount } = vi.hoisted(() => ({ getUnreadCount: vi.fn() }))
|
||||
vi.mock('@/modules/notifications/api/notificationCenter', () => ({ getUnreadCount }))
|
||||
|
||||
const CachedPage = defineComponent({
|
||||
setup() {
|
||||
const count = ref(0)
|
||||
@@ -29,14 +26,13 @@ async function mountLayout(path = '/') {
|
||||
const auth = useAuthStore()
|
||||
auth.token = 'token'
|
||||
auth.user = { id: '1', name: '平台管理员', account: 'admin', roles: ['PLATFORM_ADMIN'] }
|
||||
auth.permissions = ['platform:warehouse:list', 'platform:notification:view']
|
||||
auth.permissions = ['platform:warehouse:list', 'platform:notification:task:list', 'platform:notification:template:list']
|
||||
const router = createRouter({
|
||||
history: createMemoryHistory(),
|
||||
routes: [
|
||||
{ path: '/', name: 'home', component: { template: '<p>page</p>' }, meta: { requiresAuth: true, workspaceTab: false, keepAlive: false } },
|
||||
{ path: '/customers', name: 'customers', component: CachedPage, meta: { title: '客户管理', showPageHeading: false, requiresAuth: true } },
|
||||
{ path: '/warehouses', name: 'warehouses', component: { template: '<p>warehouses</p>' }, meta: { title: '仓库管理', requiresAuth: true } },
|
||||
{ path: '/notifications/mine', name: 'my-notifications', component: { template: '<p>messages</p>' }, meta: { title: '我的消息', requiresAuth: true } },
|
||||
{ path: '/login', component: { template: '<p>login</p>' } },
|
||||
],
|
||||
})
|
||||
@@ -49,15 +45,16 @@ async function mountLayout(path = '/') {
|
||||
}
|
||||
|
||||
describe('AppLayout', () => {
|
||||
beforeEach(() => { vi.mocked(ElMessageBox.confirm).mockReset(); getUnreadCount.mockReset().mockResolvedValue(4) })
|
||||
beforeEach(() => { vi.mocked(ElMessageBox.confirm).mockReset() })
|
||||
|
||||
it('shows the authenticated identity without a role preview switcher', async () => {
|
||||
const { wrapper } = await mountLayout()
|
||||
expect(wrapper.text()).toContain('登录身份:平台管理员(admin)')
|
||||
expect(wrapper.text()).not.toContain('权限预览')
|
||||
await flushPromises()
|
||||
expect(wrapper.get('[aria-label="消息中心,4 条未读"]').attributes('disabled')).toBeUndefined()
|
||||
expect(wrapper.get('.notification-count').text()).toBe('4')
|
||||
expect(wrapper.get('[aria-label="消息中心"]').attributes('disabled')).toBeUndefined()
|
||||
expect(wrapper.text()).toContain('消息管理')
|
||||
expect(wrapper.text()).toContain('模板管理')
|
||||
expect(wrapper.text()).not.toContain('我的消息')
|
||||
})
|
||||
|
||||
it('hides the shell heading when the route opts out', async () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Bell, FileText, LogOut, Mail, Menu } from '@lucide/vue'
|
||||
@@ -13,7 +13,6 @@ import { useAuthStore } from '@/core/auth/auth'
|
||||
import { useUnsavedWorkStore } from '@/core/unsaved-work/unsavedWork'
|
||||
import { canAccessWorkspaceRoute, workspaceRouteId, workspaceTabCandidate } from '@/core/workspace-tabs/routeWorkspace'
|
||||
import { MAX_CACHED_WORKSPACE_VIEWS, useWorkspaceTabsStore } from '@/core/workspace-tabs/workspaceTabs'
|
||||
import { getUnreadCount } from '@/modules/notifications/api/notificationCenter'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
@@ -27,8 +26,10 @@ const pageTitle = computed(() => route.meta.title || '管理后台')
|
||||
const pageDescription = computed(() => route.meta.description || '')
|
||||
const showPageHeading = computed(() => route.meta.showPageHeading !== false)
|
||||
const menuCollapsed = computed(() => collapsed.value && !mobileMenuOpen.value)
|
||||
const unreadCount = ref<number | null>(null)
|
||||
const unreadLoading = ref(false)
|
||||
const canOpenNotificationManagement = computed(() => (
|
||||
access.can('platform:notification:task:list')
|
||||
|| access.can('platform:notification:template:list')
|
||||
))
|
||||
|
||||
access.initializeAfterLogin()
|
||||
workspace.initialize(auth.user?.id || auth.user?.account || '')
|
||||
@@ -95,12 +96,6 @@ function handleMenuToggle() {
|
||||
collapsed.value = !collapsed.value
|
||||
}
|
||||
|
||||
async function loadUnreadCount() {
|
||||
if (!access.can('platform:notification:view')) return
|
||||
unreadLoading.value = true
|
||||
try { unreadCount.value = await getUnreadCount() } catch { unreadCount.value = null } finally { unreadLoading.value = false }
|
||||
}
|
||||
|
||||
function openNotificationPage(path: string) { void router.push(path) }
|
||||
|
||||
watch(() => route.fullPath, () => {
|
||||
@@ -114,8 +109,6 @@ watch(() => auth.user?.id || auth.user?.account || '', (userId, previousUserId)
|
||||
workspace.initialize(userId)
|
||||
void pruneInaccessibleTabs().then(syncCurrentRoute)
|
||||
})
|
||||
onMounted(() => { window.addEventListener('cc-notification-unread-changed', loadUnreadCount); void loadUnreadCount() })
|
||||
onBeforeUnmount(() => window.removeEventListener('cc-notification-unread-changed', loadUnreadCount))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -142,14 +135,13 @@ onBeforeUnmount(() => window.removeEventListener('cc-notification-unread-changed
|
||||
><Menu :size="22" /></button>
|
||||
<div v-if="showPageHeading" class="page-heading"><h1>{{ pageTitle }}</h1><p v-if="pageDescription">{{ pageDescription }}</p></div>
|
||||
<div class="header-user">
|
||||
<el-dropdown v-if="access.can('platform:notification:view')" trigger="click" placement="bottom-end">
|
||||
<button class="notification-trigger" type="button" :aria-label="`消息中心,${unreadCount ?? '未知'} 条未读`">
|
||||
<Bell :size="18" /><span>消息中心</span><span class="notification-count">{{ unreadLoading ? '…' : unreadCount ?? '!' }}</span>
|
||||
<el-dropdown v-if="canOpenNotificationManagement" trigger="click" placement="bottom-end">
|
||||
<button class="notification-trigger" type="button" aria-label="消息中心">
|
||||
<Bell :size="18" /><span>消息中心</span>
|
||||
</button>
|
||||
<template #dropdown><el-dropdown-menu>
|
||||
<el-dropdown-item v-if="access.can('platform:notification:task:list')" @click="openNotificationPage('/notifications/messages')"><Mail :size="15" />消息管理</el-dropdown-item>
|
||||
<el-dropdown-item v-if="access.can('platform:notification:template:list')" @click="openNotificationPage('/notifications/templates')"><FileText :size="15" />模板管理</el-dropdown-item>
|
||||
<el-dropdown-item @click="openNotificationPage('/notifications/mine')"><Bell :size="15" />我的消息</el-dropdown-item>
|
||||
</el-dropdown-menu></template>
|
||||
</el-dropdown>
|
||||
<div class="user-avatar">{{ auth.user?.name.slice(0, 1) }}</div>
|
||||
@@ -178,6 +170,5 @@ onBeforeUnmount(() => window.removeEventListener('cc-notification-unread-changed
|
||||
.app-header--without-page-heading .header-user { margin-left: auto; }
|
||||
.notification-trigger { display: inline-flex; min-height: 34px; align-items: center; gap: 6px; padding: 0 9px; border: 0; border-radius: 4px; background: transparent; color: var(--cc-muted); font: inherit; }
|
||||
.notification-trigger { cursor: pointer; }
|
||||
.notification-count { display: inline-flex; min-width: 22px; height: 18px; align-items: center; justify-content: center; padding: 0 5px; border-radius: 9px; background: #e5e7eb; color: #64748b; font-size: 11px; font-weight: 700; }
|
||||
@media (max-width: 720px) { .notification-trigger > span:not(.notification-count) { display: none; } }
|
||||
@media (max-width: 720px) { .notification-trigger > span { display: none; } }
|
||||
</style>
|
||||
|
||||
@@ -95,13 +95,13 @@ describe('admin router access metadata', () => {
|
||||
const expected = [
|
||||
['notification-messages', '/notifications/messages', 'platform:notification:task:list'],
|
||||
['notification-templates', '/notifications/templates', 'platform:notification:template:list'],
|
||||
['my-notifications', '/notifications/mine', 'platform:notification:view'],
|
||||
]
|
||||
for (const [name, path, permission] of expected) {
|
||||
const route = router.getRoutes().find((item) => item.name === name)
|
||||
expect(route?.path).toBe(path)
|
||||
expect(route?.meta.permission).toBe(permission)
|
||||
}
|
||||
expect(router.getRoutes().find((item) => item.name === 'my-notifications')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('redirects the removed enterprise review route to the protected customer list', () => {
|
||||
|
||||
Reference in New Issue
Block a user