fix: 统一图库图标间距与提示
统一四个图库操作图标的间距,为布局按钮补充悬浮说明,并在移动端禁用全部 Popover。
This commit is contained in:
+28
-31
@@ -1,51 +1,49 @@
|
|||||||
# Design QA
|
# Design QA
|
||||||
|
|
||||||
- Source visual truth:
|
- Source visual truth: `/workspace/scratch/6e52996114b8/upload/18a4bcb0-1b3a-43c8-b459-deef213b5c2f.png`
|
||||||
- `/workspace/scratch/6e52996114b8/upload/ff48911d-ddad-4f26-8607-08b588b13a47.png`
|
- Source pixel dimensions: `235 × 137`.
|
||||||
- `/workspace/scratch/6e52996114b8/upload/bd89ef3b-1804-44e7-b00c-d376e735fd70.png`
|
- Intended implementation viewport: responsive dark theme; desktop shows hover explanations and mobile disables them at `760px` and below.
|
||||||
- Source pixel dimensions: `378 × 58` and `282 × 124`.
|
|
||||||
- Intended implementation viewport: responsive dark theme, with the focused mobile filter region at approximately `393px` CSS width.
|
|
||||||
- Implementation screenshot: unavailable.
|
- Implementation screenshot: unavailable.
|
||||||
- CSS size and density normalization: unavailable because the implementation could not be opened in the cloud browser.
|
- CSS size and density normalization: unavailable because the implementation could not be opened in the cloud browser.
|
||||||
- State: 形态图片页默认筛选与网格布局。
|
- State: 形态图片页默认筛选、网格布局、四个图标操作可见。
|
||||||
|
|
||||||
## Full-view comparison evidence
|
## Full-view comparison evidence
|
||||||
|
|
||||||
The first source crop shows a separate top row containing the main-image count, refresh action, and batch-image action. The second crop identifies the display-control row after the result count and grid/masonry choices as the requested destination.
|
The source crop shows four adjacent icon buttons with two different spacing values: the grid/masonry pair is tightly grouped while refresh and batch image have visibly larger gaps.
|
||||||
|
|
||||||
The separate top row is now removed. The refresh and batch-image actions are icon-only buttons placed immediately after the grid/masonry group. Both keep accessible labels and use `NPopover` for visible hover explanations. The batch configuration drawer remains mounted without rendering its original trigger.
|
The implementation now places all four controls in one flex group with a shared `8px` gap. Grid, masonry, refresh, and batch image each use `NPopover`; all four popovers share the same responsive `disabled` state and are disabled at `max-width: 760px`.
|
||||||
|
|
||||||
A local Sites preview started successfully, but the cloud browser rejected `terminal.local` with `net::ERR_BLOCKED_BY_CLIENT`. Therefore no browser-rendered implementation screenshot is available.
|
A local Sites preview started successfully, but the cloud browser rejected `terminal.local` with `net::ERR_BLOCKED_BY_CLIENT` on both attempts. Therefore no browser-rendered implementation screenshot is available.
|
||||||
|
|
||||||
## Focused region comparison evidence
|
## Focused region comparison evidence
|
||||||
|
|
||||||
- Removed content: the standalone `主图 x / y` summary is no longer rendered.
|
- Control order: grid → masonry → refresh → batch image.
|
||||||
- Action order: result count → grid → masonry → refresh → batch image.
|
- Spacing: all four controls are direct children of the same `gallery-layout-switch` flex group with `gap: 8px`.
|
||||||
- Icon controls: refresh and batch image use the shared square `icon-button` sizing.
|
- Desktop explanations: all four controls use hover-triggered `NPopover` copy.
|
||||||
- Explanations: both new icon controls have hover-triggered `NPopover` copy.
|
- Mobile behavior: all four popovers bind `:disabled="isMobile"`; the media query follows `(max-width: 760px)`.
|
||||||
- Drawer behavior: the external batch icon owns `aria-expanded` and opens the existing configuration drawer.
|
- Accessibility: every icon-only button retains a stable Chinese `aria-label`; native `title` attributes were removed from the first two buttons.
|
||||||
|
|
||||||
These are code-level checks only and do not replace a rendered focused-region comparison.
|
These are code-level checks only and do not replace a rendered focused-region comparison.
|
||||||
|
|
||||||
## Required fidelity surfaces
|
## Required fidelity surfaces
|
||||||
|
|
||||||
- Fonts and typography: the removed labels no longer consume toolbar width; popover typography remains Naive UI-managed.
|
- Fonts and typography: no visible toolbar type styles changed; popover typography remains Naive UI-managed.
|
||||||
- Spacing and layout rhythm: actions are consolidated into the existing display-control flex row.
|
- Spacing and layout rhythm: the four-button group now uses one uniform `8px` rhythm.
|
||||||
- Colors and visual tokens: existing button states and theme tokens are unchanged.
|
- Colors and visual tokens: selected and unselected button colors are unchanged.
|
||||||
- Image quality and asset fidelity: no image behavior changed.
|
- Image quality and asset fidelity: no image behavior changed.
|
||||||
- Copy and content: `刷新图库` and `批量生图` move from button text into popover and accessible labels; receipt state remains visible in the batch popover.
|
- Copy and content: desktop popovers explain `网格布局`, `瀑布流布局`, `刷新图库`, and `批量生图`; mobile keeps accessible names without hover overlays.
|
||||||
|
|
||||||
## Findings
|
## Findings
|
||||||
|
|
||||||
- [P1] Browser-rendered comparison unavailable
|
- [P1] Browser-rendered comparison unavailable
|
||||||
- Location: 形态图片筛选区的显示与操作按钮。
|
- Location: 形态图片筛选区的四个图标按钮。
|
||||||
- Evidence: the required cloud browser returns `net::ERR_BLOCKED_BY_CLIENT` for the healthy local preview.
|
- Evidence: the required cloud browser returns `net::ERR_BLOCKED_BY_CLIENT` for the healthy local preview on two attempts.
|
||||||
- Impact: exact wrapping, popover position, hover behavior, drawer transition, and console state cannot be visually certified.
|
- Impact: exact pixel spacing, popover placement, hover visibility, responsive disabling, and console state cannot be visually certified.
|
||||||
- Fix: capture the page in an available browser at the requested responsive widths and compare the consolidated control row.
|
- Fix: capture the page in an available browser at desktop and mobile widths and compare the four-button control group.
|
||||||
|
|
||||||
## Primary interactions tested
|
## Primary interactions tested
|
||||||
|
|
||||||
Automated tests verify the new accessible refresh trigger, removal of the old heading row, batch drawer opening through the relocated trigger, and all existing refresh/batch workflows. All 275 tests pass. Browser hover interaction was unavailable.
|
Automated tests verify the exact four-button order, layout switching, refresh behavior, four desktop popover bindings, responsive disabled binding, accessible labels, and uniform generated `8px` CSS gap. All 275 tests pass. Browser hover and touch interaction were unavailable.
|
||||||
|
|
||||||
## Console errors checked
|
## Console errors checked
|
||||||
|
|
||||||
@@ -53,16 +51,15 @@ Not checked because the page could not be opened in the cloud browser.
|
|||||||
|
|
||||||
## Comparison history
|
## Comparison history
|
||||||
|
|
||||||
- Iteration 1: identified duplicated vertical chrome and text-heavy actions outside the filter controls.
|
- Iteration 1: the source identified inconsistent spacing and missing explanations on the first two controls.
|
||||||
- Iteration 2: removed the standalone summary row, consolidated two icon-only actions after the layout controls, added popover explanations, and passed the full test/build suite. Browser capture remained blocked.
|
- Iteration 2: consolidated all four controls into one spacing group, added popovers to grid and masonry, disabled all four popovers on mobile, and passed the full test/build suite. Browser capture remained blocked.
|
||||||
|
|
||||||
## Implementation checklist
|
## Implementation checklist
|
||||||
|
|
||||||
- [x] Remove the standalone main-image summary.
|
- [x] Give all four icon buttons the same gap.
|
||||||
- [x] Convert refresh and batch image actions to icon-only buttons.
|
- [x] Add `NPopover` explanations to grid and masonry.
|
||||||
- [x] Move both actions after the layout switch.
|
- [x] Disable all four `NPopover` components on mobile.
|
||||||
- [x] Add `NPopover` explanations and accessible labels.
|
- [x] Preserve accessible labels and existing button behavior.
|
||||||
- [x] Preserve batch drawer and refresh behavior.
|
- [ ] Capture and compare desktop hover and mobile touch states in the cloud browser.
|
||||||
- [ ] Capture and compare the rendered responsive state in the cloud browser.
|
|
||||||
|
|
||||||
final result: blocked
|
final result: blocked
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
NSelect,
|
NSelect,
|
||||||
NTag
|
NTag
|
||||||
} from 'naive-ui'
|
} from 'naive-ui'
|
||||||
import { computed, ref, watch, type Directive } from 'vue'
|
import { computed, onScopeDispose, ref, watch, type Directive } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import {
|
import {
|
||||||
Search,
|
Search,
|
||||||
@@ -122,6 +122,15 @@ const impactOptions = computed(() => {
|
|||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
const toolsOpen = ref(false)
|
const toolsOpen = ref(false)
|
||||||
|
/** 移动端禁用纯图标按钮的悬浮说明,避免触摸操作弹出多余浮层。 */
|
||||||
|
const isMobile = ref(false)
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
const mobileMedia = window.matchMedia('(max-width: 760px)')
|
||||||
|
const syncMobile = () => (isMobile.value = mobileMedia.matches)
|
||||||
|
syncMobile()
|
||||||
|
mobileMedia.addEventListener('change', syncMobile)
|
||||||
|
onScopeDispose(() => mobileMedia.removeEventListener('change', syncMobile))
|
||||||
|
}
|
||||||
/** 新批量回执到达时显示诊断,进入页面不自动遮挡图库。 */
|
/** 新批量回执到达时显示诊断,进入页面不自动遮挡图库。 */
|
||||||
watch(
|
watch(
|
||||||
() => session.value.receipt,
|
() => session.value.receipt,
|
||||||
@@ -568,7 +577,10 @@ async function showStaleForms() {
|
|||||||
/>
|
/>
|
||||||
<div class="form-image-display-controls">
|
<div class="form-image-display-controls">
|
||||||
<span class="filter-result-count">{{ filtered.length }} 个形态</span>
|
<span class="filter-result-count">{{ filtered.length }} 个形态</span>
|
||||||
<div class="gallery-layout-switch" role="group" aria-label="图库显示方式">
|
<!-- 四个纯图标按钮使用统一间距;桌面端通过 Popover 解释用途。 -->
|
||||||
|
<div class="gallery-layout-switch" role="group" aria-label="图库视图与操作">
|
||||||
|
<NPopover trigger="hover" placement="top" :disabled="isMobile">
|
||||||
|
<template #trigger>
|
||||||
<NButton
|
<NButton
|
||||||
class="icon-button layout-option-button"
|
class="icon-button layout-option-button"
|
||||||
:class="{ 'layout-option-active': layout === 'grid' }"
|
:class="{ 'layout-option-active': layout === 'grid' }"
|
||||||
@@ -576,11 +588,15 @@ async function showStaleForms() {
|
|||||||
:type="layout === 'grid' ? 'primary' : 'default'"
|
:type="layout === 'grid' ? 'primary' : 'default'"
|
||||||
:aria-pressed="layout === 'grid'"
|
:aria-pressed="layout === 'grid'"
|
||||||
aria-label="网格布局"
|
aria-label="网格布局"
|
||||||
title="网格布局"
|
|
||||||
@click="layout = 'grid'"
|
@click="layout = 'grid'"
|
||||||
>
|
>
|
||||||
<template #icon><LayoutGrid :size="16" /></template>
|
<template #icon><LayoutGrid :size="16" /></template>
|
||||||
</NButton>
|
</NButton>
|
||||||
|
</template>
|
||||||
|
网格布局
|
||||||
|
</NPopover>
|
||||||
|
<NPopover trigger="hover" placement="top" :disabled="isMobile">
|
||||||
|
<template #trigger>
|
||||||
<NButton
|
<NButton
|
||||||
class="icon-button layout-option-button"
|
class="icon-button layout-option-button"
|
||||||
:class="{ 'layout-option-active': layout === 'masonry' }"
|
:class="{ 'layout-option-active': layout === 'masonry' }"
|
||||||
@@ -588,14 +604,14 @@ async function showStaleForms() {
|
|||||||
:type="layout === 'masonry' ? 'primary' : 'default'"
|
:type="layout === 'masonry' ? 'primary' : 'default'"
|
||||||
:aria-pressed="layout === 'masonry'"
|
:aria-pressed="layout === 'masonry'"
|
||||||
aria-label="瀑布流布局"
|
aria-label="瀑布流布局"
|
||||||
title="瀑布流布局"
|
|
||||||
@click="layout = 'masonry'"
|
@click="layout = 'masonry'"
|
||||||
>
|
>
|
||||||
<template #icon><Columns3 :size="16" /></template>
|
<template #icon><Columns3 :size="16" /></template>
|
||||||
</NButton>
|
</NButton>
|
||||||
</div>
|
</template>
|
||||||
<!-- 高频图库操作与显示方式连续排列;纯图标按钮通过 Popover 解释用途。 -->
|
瀑布流布局
|
||||||
<NPopover trigger="hover" placement="top">
|
</NPopover>
|
||||||
|
<NPopover trigger="hover" placement="top" :disabled="isMobile">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<NButton
|
<NButton
|
||||||
class="icon-button"
|
class="icon-button"
|
||||||
@@ -614,7 +630,7 @@ async function showStaleForms() {
|
|||||||
</template>
|
</template>
|
||||||
刷新图库
|
刷新图库
|
||||||
</NPopover>
|
</NPopover>
|
||||||
<NPopover trigger="hover" placement="top">
|
<NPopover trigger="hover" placement="top" :disabled="isMobile">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<NButton
|
<NButton
|
||||||
class="icon-button"
|
class="icon-button"
|
||||||
@@ -628,12 +644,15 @@ async function showStaleForms() {
|
|||||||
<template #icon><Settings2 :size="16" /></template>
|
<template #icon><Settings2 :size="16" /></template>
|
||||||
</NButton>
|
</NButton>
|
||||||
</template>
|
</template>
|
||||||
{{ session.receipt || session.promptReceipt ? '批量生图(有回执)' : '批量生图' }}
|
{{
|
||||||
|
session.receipt || session.promptReceipt ? '批量生图(有回执)' : '批量生图'
|
||||||
|
}}
|
||||||
</NPopover>
|
</NPopover>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div v-if="showImpactDetails" class="asset-impact-context" aria-label="镜头与素材定位">
|
<div v-if="showImpactDetails" class="asset-impact-context" aria-label="镜头与素材定位">
|
||||||
<p v-if="sourceMissing" class="text-danger">来源镜头不存在或不属于当前项目,未读取其素材。</p>
|
<p v-if="sourceMissing" class="text-danger">来源镜头不存在或不属于当前项目,未读取其素材。</p>
|
||||||
<p v-if="sourceCurrent" class="text-xs">
|
<p v-if="sourceCurrent" class="text-xs">
|
||||||
@@ -920,7 +939,7 @@ async function showStaleForms() {
|
|||||||
@apply flex items-center justify-self-end gap-3;
|
@apply flex items-center justify-self-end gap-3;
|
||||||
}
|
}
|
||||||
.gallery-layout-switch {
|
.gallery-layout-switch {
|
||||||
@apply flex items-center gap-1;
|
@apply flex items-center gap-2;
|
||||||
}
|
}
|
||||||
/* 未选中的排版按钮保留浅灰底,和筛选容器区分,同时不覆盖选中项的绿色状态。 */
|
/* 未选中的排版按钮保留浅灰底,和筛选容器区分,同时不覆盖选中项的绿色状态。 */
|
||||||
.gallery-layout-switch .layout-option-button:not(.layout-option-active) {
|
.gallery-layout-switch .layout-option-button:not(.layout-option-active) {
|
||||||
|
|||||||
@@ -186,9 +186,13 @@ describe('全站平面主题', () => {
|
|||||||
const source = readFileSync('src/features/subject-images/SubjectImagesPage.vue', 'utf8')
|
const source = readFileSync('src/features/subject-images/SubjectImagesPage.vue', 'utf8')
|
||||||
expect(source).not.toContain('class="toolbar-summary"')
|
expect(source).not.toContain('class="toolbar-summary"')
|
||||||
expect(source).toMatch(/<WorkspaceTools\s+triggerless/)
|
expect(source).toMatch(/<WorkspaceTools\s+triggerless/)
|
||||||
expect(source.match(/<NPopover trigger="hover" placement="top">/g)).toHaveLength(2)
|
expect(source.match(/<NPopover trigger="hover" placement="top" :disabled="isMobile">/g)).toHaveLength(4)
|
||||||
|
expect(source).toMatch(/aria-label="网格布局"/)
|
||||||
|
expect(source).toMatch(/aria-label="瀑布流布局"/)
|
||||||
expect(source).toMatch(/aria-label="刷新图库"/)
|
expect(source).toMatch(/aria-label="刷新图库"/)
|
||||||
expect(source).toMatch(/data-workspace-tools\s+aria-label="批量生图"/)
|
expect(source).toMatch(/data-workspace-tools\s+aria-label="批量生图"/)
|
||||||
|
expect(source).not.toContain('title="网格布局"')
|
||||||
|
expect(source).not.toContain('title="瀑布流布局"')
|
||||||
expect(css).toMatch(
|
expect(css).toMatch(
|
||||||
/\.form-image-filter-region\s*\{[^}]*padding:\s*12px;[^}]*--app-field:\s*var\(--app-surface\);[^}]*--app-field-hover:\s*var\(--app-surface\);[^}]*background:\s*color-mix\(in srgb, var\(--app-subtle\) 96%, white\);/
|
/\.form-image-filter-region\s*\{[^}]*padding:\s*12px;[^}]*--app-field:\s*var\(--app-surface\);[^}]*--app-field-hover:\s*var\(--app-surface\);[^}]*background:\s*color-mix\(in srgb, var\(--app-subtle\) 96%, white\);/
|
||||||
)
|
)
|
||||||
@@ -209,6 +213,9 @@ describe('全站平面主题', () => {
|
|||||||
expect(css).toMatch(
|
expect(css).toMatch(
|
||||||
/\.gallery-layout-switch \.layout-option-button:not\(\.layout-option-active\)\s*\{[^}]*background:\s*var\(--app-control-hover\);/
|
/\.gallery-layout-switch \.layout-option-button:not\(\.layout-option-active\)\s*\{[^}]*background:\s*var\(--app-control-hover\);/
|
||||||
)
|
)
|
||||||
|
expect(css).toMatch(
|
||||||
|
/\.gallery-layout-switch\s*\{[^}]*display:\s*flex;[^}]*align-items:\s*center;[^}]*gap:\s*8px;/
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('单图标按钮以当前控件高度为边长,尺寸与内容按钮互不影响', () => {
|
it('单图标按钮以当前控件高度为边长,尺寸与内容按钮互不影响', () => {
|
||||||
|
|||||||
@@ -143,6 +143,12 @@ describe('过期首帧到具体素材定位', () => {
|
|||||||
expect(wrapper!.get('[aria-label="网格布局"]').attributes('aria-pressed')).toBe('true')
|
expect(wrapper!.get('[aria-label="网格布局"]').attributes('aria-pressed')).toBe('true')
|
||||||
expect(wrapper!.get('[aria-label="网格布局"]').classes()).toContain('layout-option-active')
|
expect(wrapper!.get('[aria-label="网格布局"]').classes()).toContain('layout-option-active')
|
||||||
expect(wrapper!.get('[aria-label="瀑布流布局"]').classes()).not.toContain('layout-option-active')
|
expect(wrapper!.get('[aria-label="瀑布流布局"]').classes()).not.toContain('layout-option-active')
|
||||||
|
expect(
|
||||||
|
wrapper!
|
||||||
|
.get('.gallery-layout-switch')
|
||||||
|
.findAll('button')
|
||||||
|
.map(button => button.attributes('aria-label'))
|
||||||
|
).toEqual(['网格布局', '瀑布流布局', '刷新图库', '批量生图'])
|
||||||
await wrapper!.get('[aria-label="瀑布流布局"]').trigger('click')
|
await wrapper!.get('[aria-label="瀑布流布局"]').trigger('click')
|
||||||
expect(wrapper!.get('.form-image-masonry').element).toBe(grid)
|
expect(wrapper!.get('.form-image-masonry').element).toBe(grid)
|
||||||
expect(wrapper!.get('[aria-label="瀑布流布局"]').attributes('aria-pressed')).toBe('true')
|
expect(wrapper!.get('[aria-label="瀑布流布局"]').attributes('aria-pressed')).toBe('true')
|
||||||
|
|||||||
Reference in New Issue
Block a user