This commit is contained in:
2026-08-19 16:45:51 +08:00
parent d1de19dc82
commit 65cd9ecefa
79 changed files with 3397 additions and 38 deletions
+9 -2
View File
@@ -60,6 +60,7 @@ function mockViewport(isMobile: boolean) {
describe('AppLayout', () => {
beforeEach(() => {
window.localStorage.clear()
vi.mocked(ElMessageBox.confirm).mockReset()
mockViewport(false)
})
@@ -111,10 +112,16 @@ describe('AppLayout', () => {
expect(mediaQuery.removeEventListener).toHaveBeenCalledWith('change', changeHandler)
})
it('shows the authenticated identity without a role preview switcher', async () => {
it('shows the authenticated identity and the localhost full-access control', async () => {
const { wrapper } = await mountLayout()
expect(wrapper.text()).toContain('登录身份:平台管理员(admin)')
expect(wrapper.text()).not.toContain('权限预览')
const accessToggle = wrapper.get('[role="switch"]')
expect(accessToggle.text()).toContain('本地全权限')
expect(accessToggle.text()).toContain('已关闭')
expect(accessToggle.attributes('aria-checked')).toBe('false')
await accessToggle.trigger('click')
expect(accessToggle.text()).toContain('已开启')
expect(accessToggle.attributes('aria-checked')).toBe('true')
expect(wrapper.get('[aria-label="消息中心"]').attributes('disabled')).toBeUndefined()
expect(wrapper.text()).toContain('消息管理')
expect(wrapper.text()).toContain('模板管理')
+20 -2
View File
@@ -2,7 +2,7 @@
import { computed, onBeforeUnmount, onMounted, 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'
import { Bell, FileText, LogOut, Mail, Menu, ShieldCheck } from '@lucide/vue'
import { getApiErrorDetails } from '@/shared/api/http'
import baiyueLogoWhite from '@/assets/brand/baiyue-logo-white.png'
import baiyueMarkWhite from '@/assets/brand/baiyue-mark-white.png'
@@ -159,6 +159,19 @@ watch(() => auth.user?.id || auth.user?.account || '', (userId, previousUserId)
<!-- <h1>{{ pageTitle }}</h1><p v-if="pageDescription">{{ pageDescription }}</p> -->
</div>
<div class="header-user">
<button
v-if="access.localFullAccessAvailable"
class="local-full-access-toggle"
type="button"
role="switch"
:aria-checked="access.localFullAccessEnabled"
:title="access.localFullAccessEnabled ? '关闭后恢复服务端权限' : '开启后显示全部前端页面和按钮'"
@click="access.toggleLocalFullAccess"
>
<ShieldCheck :size="17" />
<span>本地全权限</span>
<strong>{{ access.localFullAccessEnabled ? '已开启' : '已关闭' }}</strong>
</button>
<el-dropdown v-if="canOpenNotificationManagement" trigger="click" placement="bottom-end">
<button class="notification-trigger" type="button" aria-label="消息中心">
<Bell :size="18" /><span>消息中心</span>
@@ -192,9 +205,14 @@ watch(() => auth.user?.id || auth.user?.account || '', (userId, previousUserId)
<style scoped>
.app-header--without-page-heading .header-user { margin-left: auto; }
.local-full-access-toggle { display: inline-flex; height: 38px; align-items: center; gap: 7px; padding: 0 11px; border: 1px solid rgba(255, 255, 255, .3); border-radius: 10px; background: rgba(255, 255, 255, .12); color: #fff; font: inherit; font-size: 13px; cursor: pointer; transition: background .2s ease, border-color .2s ease; }
.local-full-access-toggle:hover { border-color: rgba(255, 255, 255, .55); background: rgba(255, 255, 255, .22); }
.local-full-access-toggle:focus-visible { outline: 2px solid rgba(255, 255, 255, .9); outline-offset: 2px; }
.local-full-access-toggle[aria-checked="true"] { border-color: #86efac; background: rgba(22, 163, 74, .3); }
.local-full-access-toggle strong { color: #dcfce7; font-size: 12px; }
.notification-trigger { display: inline-flex; height: 38px; align-items: center; gap: 7px; padding: 0 12px; border: 1px solid rgba(255, 255, 255, .28); border-radius: 10px; background: rgba(255, 255, 255, .12); color: #fff; font: inherit; font-size: 13px; font-weight: 600; cursor: pointer; transition: background .2s ease, border-color .2s ease, transform .2s ease; }
.notification-trigger:hover { border-color: rgba(255, 255, 255, .48); background: rgba(255, 255, 255, .24); transform: translateY(-1px); }
.notification-trigger:focus-visible { outline: 2px solid rgba(255, 255, 255, .9); outline-offset: 2px; }
.notification-trigger > svg { flex: 0 0 auto; }
@media (max-width: 720px) { .notification-trigger { width: 44px; height: 44px; justify-content: center; padding: 0; } .notification-trigger > span { display: none; } }
@media (max-width: 720px) { .local-full-access-toggle > span { display: none; } .notification-trigger { width: 44px; height: 44px; justify-content: center; padding: 0; } .notification-trigger > span { display: none; } }
</style>