fix(viewer): remove topbar settings menu, keep locale switcher only (user request)

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-09-23 10:38:23 +08:00
parent 2409beb7c2
commit fd35c712a5
2 changed files with 5 additions and 136 deletions
+1 -124
View File
@@ -1,9 +1,8 @@
<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref, watchEffect } from 'vue';
import { computed, watchEffect } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import LocaleSwitcher from './components/LocaleSwitcher.vue';
import { reducedMotion, toggleReducedMotion } from './ui/prefs';
// UI-02 V01 视觉 token:变量全部作用域在 .viewer-shell 类上(非 :root 全局),
// embed 分支不挂该类 → 嵌入 iframe 不消费任何 token(隔离红线)。
import './styles/viewer-tokens.css';
@@ -20,34 +19,6 @@ watchEffect(() => {
document.title = t('app.title');
document.documentElement.setAttribute('lang', locale.value);
});
// —— 顶栏轻量菜单(UI-02 §3.2):减少动画入口收进语言旁「⋯」,可发现但不占整行 ——
const prefsOpen = ref(false);
const prefsRootEl = ref<HTMLElement | null>(null);
function closePrefs(): void {
prefsOpen.value = false;
}
function onPrefsKeydown(event: KeyboardEvent): void {
if (event.key === 'Escape') closePrefs();
}
function onOuterPointerDown(event: PointerEvent): void {
if (prefsOpen.value && prefsRootEl.value !== null && !prefsRootEl.value.contains(event.target as Node)) {
closePrefs();
}
}
onMounted(() => {
document.addEventListener('pointerdown', onOuterPointerDown, true);
document.addEventListener('keydown', onPrefsKeydown);
});
onBeforeUnmount(() => {
document.removeEventListener('pointerdown', onOuterPointerDown, true);
document.removeEventListener('keydown', onPrefsKeydown);
});
</script>
<template>
@@ -63,34 +34,6 @@ onBeforeUnmount(() => {
</span>
<div class="topbar-actions">
<LocaleSwitcher />
<div ref="prefsRootEl" class="prefs-menu">
<button
type="button"
class="icon-btn"
:aria-label="t('viewer.settings')"
aria-haspopup="menu"
:aria-expanded="prefsOpen"
@click="prefsOpen = !prefsOpen"
>
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<circle cx="5" cy="12" r="2" fill="currentColor" />
<circle cx="12" cy="12" r="2" fill="currentColor" />
<circle cx="19" cy="12" r="2" fill="currentColor" />
</svg>
</button>
<div v-if="prefsOpen" class="prefs-popover" role="menu" :aria-label="t('viewer.settings')">
<button
type="button"
class="prefs-item"
role="menuitemcheckbox"
:aria-checked="reducedMotion"
@click="toggleReducedMotion"
>
<span class="prefs-check" aria-hidden="true">{{ reducedMotion ? '✓' : '' }}</span>
{{ t('viewer.reduceMotion') }}
</button>
</div>
</div>
</div>
</header>
<main class="viewer-main">
@@ -166,72 +109,6 @@ body {
align-items: center;
gap: 4px;
}
.prefs-menu {
position: relative;
}
.icon-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: var(--viewer-touch-target);
height: var(--viewer-touch-target);
border: none;
border-radius: 50%;
background: transparent;
color: var(--viewer-text-secondary);
cursor: pointer;
}
.icon-btn:hover {
background: var(--viewer-page-bg);
}
.icon-btn:focus-visible {
outline: 2px solid var(--viewer-primary);
outline-offset: -2px;
}
.icon-btn svg {
width: 22px;
height: 22px;
}
.prefs-popover {
position: absolute;
top: calc(100% + 2px);
right: 0;
z-index: 130;
min-width: 176px;
border: 1px solid var(--viewer-divider);
border-radius: 12px;
background: var(--viewer-surface);
box-shadow: 0 8px 24px rgba(23, 32, 51, 0.12);
padding: 4px;
display: grid;
}
.prefs-item {
display: flex;
align-items: center;
gap: 8px;
min-height: var(--viewer-touch-target);
border: none;
border-radius: 8px;
background: transparent;
color: var(--viewer-text);
padding: 0 12px;
font-size: 0.875rem;
text-align: left;
cursor: pointer;
}
.prefs-item:hover {
background: var(--viewer-page-bg);
}
.prefs-item:focus-visible {
outline: 2px solid var(--viewer-primary);
outline-offset: -2px;
}
.prefs-check {
display: inline-block;
width: 1em;
color: var(--viewer-primary);
font-weight: 700;
}
/* V01 §3.2:内容页不再有全局 16px 包围——宣传图/照片边到边,内边距由
* 信息区(AlbumHeader)与照片区(PhotoFeed,Task 3)各自负责。 */
.viewer-main {
+4 -12
View File
@@ -73,7 +73,7 @@ describe('viewer shell tokens and embed isolation (UI-02 V01)', () => {
expect(css).toContain('.viewer-shell');
});
it('normal routes render .viewer-shell with PhotoLive brand top bar and prefs menu', async () => {
it('normal routes render .viewer-shell with PhotoLive brand top bar (locale only, no prefs menu)', async () => {
await router.push('/');
await router.isReady();
const app = mountApp();
@@ -86,17 +86,9 @@ describe('viewer shell tokens and embed isolation (UI-02 V01)', () => {
expect(topbar.find('.brand-name').text()).toBe('PhotoLive');
expect(topbar.find('.locale-trigger').exists()).toBe(true);
// 「⋯」轻量菜单(§3.2):减少动画入口可发现、可切换。
const more = topbar.find('.icon-btn');
expect(more.attributes('aria-haspopup')).toBe('menu');
await more.trigger('click');
const item = app.find('.prefs-item');
expect(item.text()).toBe('减少动画');
const before = item.attributes('aria-checked');
await item.trigger('click');
expect(app.find('.prefs-item').attributes('aria-checked')).toBe(before === 'true' ? 'false' : 'true');
// 还原模块级偏好单例,避免影响后续用例。
await app.find('.prefs-item').trigger('click');
// 设置菜单已按用户要求移除(2026-09-23):顶栏仅保留语言切换。
expect(topbar.find('.prefs-menu').exists()).toBe(false);
expect(topbar.find('.icon-btn').exists()).toBe(false);
});
it('embed routes keep the bare shell: no .viewer-shell, no top bar, no outer padding wrapper', async () => {