优化
This commit is contained in:
@@ -13,12 +13,13 @@ test('source contract: renders the grouped sidebar menu and mobile drawer contro
|
||||
assert.match(layoutSource, /import \{ computed, nextTick, onBeforeUnmount, onMounted, ref, watch \} from 'vue'/)
|
||||
assert.match(layoutSource, /import \{[\s\S]*?\bX\s*\} from '@lucide\/vue'/)
|
||||
assert.match(layoutSource, /import ClientSidebarMenu from '@\/components\/ClientSidebarMenu\.vue'/)
|
||||
assert.match(layoutSource, /:class="\{ 'mobile-menu-open': mobileMenuOpen \}"/)
|
||||
assert.match(layoutSource, /:class="\{ 'mobile-menu-open': mobileMenuOpen, 'is-dashboard-route': route\.path === '\/' \}"/)
|
||||
assert.match(layoutSource, /<button\s+v-if="mobileMenuOpen"[^>]*class="client-sidebar-scrim"[^>]*aria-label="关闭导航"[^>]*@click="closeMobileMenu"/s)
|
||||
assert.match(layoutSource, /<aside\s+ref="sidebarRef"\s+id="client-sidebar"\s+class="client-sidebar"\s+:inert="!desktopViewport && !mobileMenuOpen"\s+:aria-hidden="!desktopViewport && !mobileMenuOpen \? 'true' : undefined"\s*>/)
|
||||
assert.doesNotMatch(layoutSource, /<aside[^>]*aria-label=/)
|
||||
assert.match(layoutSource, /<nav aria-label="客户工作台导航">/)
|
||||
assert.match(layoutSource, /<div class="client-sidebar-brand">[\s\S]*?<RouterLink class="client-sidebar-brand-link" to="\/" aria-label="返回业务总览" @click="closeMobileMenu">[\s\S]*?alt="柏悦物流"[\s\S]*?客户工作台/s)
|
||||
assert.match(layoutSource, /<div class="client-sidebar-brand">[\s\S]*?<RouterLink class="client-sidebar-brand-link" to="\/" aria-label="返回业务总览" @click="closeMobileMenu">[\s\S]*?alt="柏悦物流"/s)
|
||||
assert.doesNotMatch(layoutSource, /client-sidebar-brand-copy/)
|
||||
assert.match(layoutSource, /<button[^>]*ref="sidebarCloseButtonRef"[^>]*aria-label="关闭导航"[^>]*@click="closeMobileMenu"[^>]*>\s*<X :size="20" \/>/s)
|
||||
assert.match(layoutSource, /<ClientSidebarMenu\s+:active-path="route\.path"\s+@navigate="closeMobileMenu"\s*\/>/s)
|
||||
assert.match(layoutSource, /<button type="button" class="client-sidebar-logout" @click="handleLogout">[\s\S]*?退出登录/s)
|
||||
@@ -45,11 +46,13 @@ test('source contract: removes the header and main content from the accessibilit
|
||||
assert.match(layoutSource, /<main\s+class="client-main"\s+:inert="mobileMenuOpen"\s+:aria-hidden="mobileMenuOpen \? 'true' : undefined"\s*>/)
|
||||
})
|
||||
|
||||
test('source contract: keeps route context and user identity in the compact header', () => {
|
||||
test('source contract: keeps the prototype greeting and fixed message and profile utilities in the compact header', () => {
|
||||
assert.match(layoutSource, /const pageTitle = computed\(\(\) => route\.meta\.title \|\| '业务总览'\)/)
|
||||
assert.match(layoutSource, /const pageDescription = computed\(\(\) => route\.meta\.description \|\| ''\)/)
|
||||
assert.match(layoutSource, /<div class="client-header-heading">\s*<h1>\{\{ pageTitle \}\}<\/h1>\s*<p v-if="pageDescription">\{\{ pageDescription \}\}<\/p>/s)
|
||||
assert.doesNotMatch(layoutSource, /const pageDescription/)
|
||||
assert.match(layoutSource, /<p class="client-header-greeting">Hi, 客户<\/p>/)
|
||||
assert.match(layoutSource, /<div class="client-user">[\s\S]*\{\{ auth\.user\?\.name \}\}/)
|
||||
assert.match(layoutSource, /class="client-header-notifications" to="\/notifications" aria-label="打开我的消息"/)
|
||||
assert.match(layoutSource, /class="client-header-profile"[\s\S]*to="\/account\/profile"[\s\S]*aria-label="打开我的信息"/)
|
||||
assert.doesNotMatch(layoutSource, /<div class="client-user">[\s\S]*aria-label="退出登录"/)
|
||||
assert.doesNotMatch(layoutSource, /Building2|tenant-name|tenantName/)
|
||||
assert.doesNotMatch(routerSource, /showPageHeading|showTenantName/)
|
||||
@@ -58,19 +61,25 @@ test('source contract: keeps route context and user identity in the compact head
|
||||
test('source contract: renders the complete route breadcrumb before page content', () => {
|
||||
assert.match(layoutSource, /import \{ getFirstAccessiblePath, getRouteBreadcrumbs \} from '@\/navigation\/clientNavigation'/)
|
||||
assert.match(layoutSource, /const breadcrumbs = computed\(\(\) => getRouteBreadcrumbs\(route\.path, String\(pageTitle\.value\)\)\)/)
|
||||
assert.match(layoutSource, /<nav class="client-breadcrumb" aria-label="页面路径">[\s\S]*?v-for="\(item, index\) in breadcrumbs"[\s\S]*?<RouterLink v-if="item\.path" :to="item\.path">[\s\S]*?<strong v-else aria-current="page">/)
|
||||
assert.match(layoutSource, /<nav v-if="route\.path !== '\/'" class="client-breadcrumb" aria-label="页面路径">[\s\S]*?v-for="\(item, index\) in breadcrumbs"[\s\S]*?<RouterLink v-if="item\.path" :to="item\.path">[\s\S]*?<strong v-else aria-current="page">/)
|
||||
assert.match(shellStyles, /\.client-breadcrumb \{[^}]*margin: 0 0 14px;/)
|
||||
})
|
||||
|
||||
test('source contract: aligns the light top bar beside the prototype-inspired dark sidebar and bounds the mobile header', () => {
|
||||
assert.match(shellStyles, /\.top-header \{[^}]*inset: 0 0 auto 250px;[^}]*max-width: calc\(100vw - 250px\);[^}]*padding: 0 24px;/)
|
||||
assert.match(shellStyles, /\.top-header \{[^}]*background: rgba\(255, 255, 255, \.96\)/)
|
||||
assert.match(shellStyles, /\.client-sidebar \{[^}]*inset: 0 auto 0 0;[^}]*width: 250px;[^}]*background: linear-gradient\(180deg, #0b1f3a 0%, #0c2a52 100%\)/)
|
||||
assert.match(shellStyles, /\.client-sidebar-brand \{[^}]*border-bottom: 1px solid rgba\(255, 255, 255, \.09\)/)
|
||||
assert.match(shellStyles, /\.client-main \{[^}]*padding: 134px 20px 40px;/)
|
||||
test('source contract: aligns the blue-tinted prototype shell around a compact sidebar and bounds the mobile header', () => {
|
||||
assert.match(shellStyles, /\.top-header \{[^}]*inset: 0 0 auto 224px;[^}]*max-width: calc\(100vw - 224px\);[^}]*padding: 0 24px;/)
|
||||
assert.match(shellStyles, /\.top-header \{[^}]*background: rgba\(255, 255, 255, \.98\)/)
|
||||
assert.match(shellStyles, /\.client-sidebar \{[^}]*inset: 0 auto 0 0;[^}]*width: 224px;[^}]*border-right: 1px solid #dfe8f3;[^}]*background: linear-gradient\(180deg, #f5f8ff 0%, #edf5fc 100%\)/)
|
||||
assert.match(shellStyles, /\.client-sidebar-brand \{[^}]*border-radius: 15px;[^}]*background: linear-gradient\(145deg, #0a2b50, #0b3d6c\)/)
|
||||
assert.match(shellStyles, /\.client-main \{[^}]*padding: 126px 20px 40px;/)
|
||||
assert.match(shellStyles, /\.is-dashboard-route \.client-main \{ padding-top: 88px; \}/)
|
||||
assert.match(globalStyles, /@media \(max-width: 720px\) \{[\s\S]*\.top-header \{ inset: 0;[^}]*padding: 0 14px; \}/)
|
||||
assert.match(globalStyles, /\.client-header-menu-button \{ display: grid; \}/)
|
||||
assert.match(globalStyles, /\.client-header-heading p, \.client-user-copy \{ display: none; \}/)
|
||||
assert.match(globalStyles, /\.client-header-greeting, \.client-user-copy \{ display: none; \}/)
|
||||
})
|
||||
|
||||
test('source contract: keeps the dashboard free of a redundant workspace tab', () => {
|
||||
assert.match(layoutSource, /<WorkspaceTabs\s+v-if="route\.path !== '\/'"/)
|
||||
assert.match(layoutSource, /'is-dashboard-route': route\.path === '\/'/)
|
||||
})
|
||||
|
||||
test('source contract: closes the mobile menu on route changes and Escape', () => {
|
||||
|
||||
+17
-10
@@ -24,7 +24,6 @@ const notifications = useNotificationsStore()
|
||||
const workspace = useWorkspaceTabsStore()
|
||||
const unsaved = useUnsavedWorkStore()
|
||||
const pageTitle = computed(() => route.meta.title || '业务总览')
|
||||
const pageDescription = computed(() => route.meta.description || '')
|
||||
const breadcrumbs = computed(() => getRouteBreadcrumbs(route.path, String(pageTitle.value)))
|
||||
const mobileMenuOpen = ref(false)
|
||||
const desktopViewport = ref(true)
|
||||
@@ -185,7 +184,7 @@ async function handleLogout() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="client-shell" :class="{ 'mobile-menu-open': mobileMenuOpen }">
|
||||
<div class="client-shell" :class="{ 'mobile-menu-open': mobileMenuOpen, 'is-dashboard-route': route.path === '/' }">
|
||||
<header class="top-header" :inert="mobileMenuOpen" :aria-hidden="mobileMenuOpen ? 'true' : undefined">
|
||||
<button
|
||||
ref="menuButtonRef"
|
||||
@@ -198,14 +197,22 @@ async function handleLogout() {
|
||||
>
|
||||
<Menu :size="20" />
|
||||
</button>
|
||||
<div class="client-header-heading">
|
||||
<h1>{{ pageTitle }}</h1>
|
||||
<p v-if="pageDescription">{{ pageDescription }}</p>
|
||||
</div>
|
||||
<p class="client-header-greeting">Hi, 客户</p>
|
||||
<div class="client-user">
|
||||
<RouterLink v-if="auth.hasPermission('merchant:notification:view')" class="client-header-notifications" to="/notifications" aria-label="打开我的消息"><Bell :size="19" /><span v-if="notifications.unreadCount !== null && notifications.unreadCount > 0" class="client-header-notifications-badge">{{ notifications.unreadCount > 99 ? '99+' : notifications.unreadCount }}</span></RouterLink>
|
||||
<span class="client-avatar">{{ auth.user?.name.slice(0, 1) }}</span>
|
||||
<span class="client-user-copy"><strong>{{ auth.user?.name }}</strong><small>{{ auth.user?.roles.join('、') }}</small></span>
|
||||
<RouterLink
|
||||
v-if="auth.hasPermission('account:profile:view')"
|
||||
class="client-header-profile"
|
||||
to="/account/profile"
|
||||
aria-label="打开我的信息"
|
||||
>
|
||||
<span class="client-avatar">{{ auth.user?.name.slice(0, 1) }}</span>
|
||||
<span class="client-user-copy"><strong>{{ auth.user?.name }}</strong><small>{{ auth.user?.roles.join('、') }}</small></span>
|
||||
</RouterLink>
|
||||
<template v-else>
|
||||
<span class="client-avatar">{{ auth.user?.name.slice(0, 1) }}</span>
|
||||
<span class="client-user-copy"><strong>{{ auth.user?.name }}</strong><small>{{ auth.user?.roles.join('、') }}</small></span>
|
||||
</template>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -227,7 +234,6 @@ async function handleLogout() {
|
||||
<div class="client-sidebar-brand">
|
||||
<RouterLink class="client-sidebar-brand-link" to="/" aria-label="返回业务总览" @click="closeMobileMenu">
|
||||
<img class="client-sidebar-brand-logo" :src="baiyueLogoDark" alt="柏悦物流" />
|
||||
<span class="client-sidebar-brand-copy"><small>客户工作台</small></span>
|
||||
</RouterLink>
|
||||
<button
|
||||
ref="sidebarCloseButtonRef"
|
||||
@@ -251,6 +257,7 @@ async function handleLogout() {
|
||||
</aside>
|
||||
|
||||
<WorkspaceTabs
|
||||
v-if="route.path !== '/'"
|
||||
:inert="mobileMenuOpen"
|
||||
:aria-hidden="mobileMenuOpen ? 'true' : undefined"
|
||||
/>
|
||||
@@ -260,7 +267,7 @@ async function handleLogout() {
|
||||
:inert="mobileMenuOpen"
|
||||
:aria-hidden="mobileMenuOpen ? 'true' : undefined"
|
||||
>
|
||||
<nav class="client-breadcrumb" aria-label="页面路径">
|
||||
<nav v-if="route.path !== '/'" class="client-breadcrumb" aria-label="页面路径">
|
||||
<template v-for="(item, index) in breadcrumbs" :key="`${item.title}-${index}`">
|
||||
<span v-if="index" aria-hidden="true">/</span>
|
||||
<RouterLink v-if="item.path" :to="item.path">{{ item.title }}</RouterLink>
|
||||
|
||||
Reference in New Issue
Block a user