fix: 统一图库图标间距与提示
统一四个图库操作图标的间距,为布局按钮补充悬浮说明,并在移动端禁用全部 Popover。
This commit is contained in:
+28
-31
@@ -1,51 +1,49 @@
|
||||
# Design QA
|
||||
|
||||
- Source visual truth:
|
||||
- `/workspace/scratch/6e52996114b8/upload/ff48911d-ddad-4f26-8607-08b588b13a47.png`
|
||||
- `/workspace/scratch/6e52996114b8/upload/bd89ef3b-1804-44e7-b00c-d376e735fd70.png`
|
||||
- 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.
|
||||
- Source visual truth: `/workspace/scratch/6e52996114b8/upload/18a4bcb0-1b3a-43c8-b459-deef213b5c2f.png`
|
||||
- Source pixel dimensions: `235 × 137`.
|
||||
- Intended implementation viewport: responsive dark theme; desktop shows hover explanations and mobile disables them at `760px` and below.
|
||||
- Implementation screenshot: unavailable.
|
||||
- CSS size and density normalization: unavailable because the implementation could not be opened in the cloud browser.
|
||||
- State: 形态图片页默认筛选与网格布局。
|
||||
- State: 形态图片页默认筛选、网格布局、四个图标操作可见。
|
||||
|
||||
## 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
|
||||
|
||||
- Removed content: the standalone `主图 x / y` summary is no longer rendered.
|
||||
- Action order: result count → grid → masonry → refresh → batch image.
|
||||
- Icon controls: refresh and batch image use the shared square `icon-button` sizing.
|
||||
- Explanations: both new icon controls have hover-triggered `NPopover` copy.
|
||||
- Drawer behavior: the external batch icon owns `aria-expanded` and opens the existing configuration drawer.
|
||||
- Control order: grid → masonry → refresh → batch image.
|
||||
- Spacing: all four controls are direct children of the same `gallery-layout-switch` flex group with `gap: 8px`.
|
||||
- Desktop explanations: all four controls use hover-triggered `NPopover` copy.
|
||||
- Mobile behavior: all four popovers bind `:disabled="isMobile"`; the media query follows `(max-width: 760px)`.
|
||||
- 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.
|
||||
|
||||
## Required fidelity surfaces
|
||||
|
||||
- Fonts and typography: the removed labels no longer consume toolbar width; popover typography remains Naive UI-managed.
|
||||
- Spacing and layout rhythm: actions are consolidated into the existing display-control flex row.
|
||||
- Colors and visual tokens: existing button states and theme tokens are unchanged.
|
||||
- Fonts and typography: no visible toolbar type styles changed; popover typography remains Naive UI-managed.
|
||||
- Spacing and layout rhythm: the four-button group now uses one uniform `8px` rhythm.
|
||||
- Colors and visual tokens: selected and unselected button colors are unchanged.
|
||||
- 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
|
||||
|
||||
- [P1] Browser-rendered comparison unavailable
|
||||
- Location: 形态图片筛选区的显示与操作按钮。
|
||||
- Evidence: the required cloud browser returns `net::ERR_BLOCKED_BY_CLIENT` for the healthy local preview.
|
||||
- Impact: exact wrapping, popover position, hover behavior, drawer transition, 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.
|
||||
- Location: 形态图片筛选区的四个图标按钮。
|
||||
- Evidence: the required cloud browser returns `net::ERR_BLOCKED_BY_CLIENT` for the healthy local preview on two attempts.
|
||||
- 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 desktop and mobile widths and compare the four-button control group.
|
||||
|
||||
## 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
|
||||
|
||||
@@ -53,16 +51,15 @@ Not checked because the page could not be opened in the cloud browser.
|
||||
|
||||
## Comparison history
|
||||
|
||||
- Iteration 1: identified duplicated vertical chrome and text-heavy actions outside the filter 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 1: the source identified inconsistent spacing and missing explanations on the first two controls.
|
||||
- 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
|
||||
|
||||
- [x] Remove the standalone main-image summary.
|
||||
- [x] Convert refresh and batch image actions to icon-only buttons.
|
||||
- [x] Move both actions after the layout switch.
|
||||
- [x] Add `NPopover` explanations and accessible labels.
|
||||
- [x] Preserve batch drawer and refresh behavior.
|
||||
- [ ] Capture and compare the rendered responsive state in the cloud browser.
|
||||
- [x] Give all four icon buttons the same gap.
|
||||
- [x] Add `NPopover` explanations to grid and masonry.
|
||||
- [x] Disable all four `NPopover` components on mobile.
|
||||
- [x] Preserve accessible labels and existing button behavior.
|
||||
- [ ] Capture and compare desktop hover and mobile touch states in the cloud browser.
|
||||
|
||||
final result: blocked
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
NSelect,
|
||||
NTag
|
||||
} 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 {
|
||||
Search,
|
||||
@@ -122,6 +122,15 @@ const impactOptions = computed(() => {
|
||||
}))
|
||||
})
|
||||
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(
|
||||
() => session.value.receipt,
|
||||
@@ -568,7 +577,10 @@ async function showStaleForms() {
|
||||
/>
|
||||
<div class="form-image-display-controls">
|
||||
<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
|
||||
class="icon-button layout-option-button"
|
||||
:class="{ 'layout-option-active': layout === 'grid' }"
|
||||
@@ -576,11 +588,15 @@ async function showStaleForms() {
|
||||
:type="layout === 'grid' ? 'primary' : 'default'"
|
||||
:aria-pressed="layout === 'grid'"
|
||||
aria-label="网格布局"
|
||||
title="网格布局"
|
||||
@click="layout = 'grid'"
|
||||
>
|
||||
<template #icon><LayoutGrid :size="16" /></template>
|
||||
</NButton>
|
||||
</template>
|
||||
网格布局
|
||||
</NPopover>
|
||||
<NPopover trigger="hover" placement="top" :disabled="isMobile">
|
||||
<template #trigger>
|
||||
<NButton
|
||||
class="icon-button layout-option-button"
|
||||
:class="{ 'layout-option-active': layout === 'masonry' }"
|
||||
@@ -588,14 +604,14 @@ async function showStaleForms() {
|
||||
:type="layout === 'masonry' ? 'primary' : 'default'"
|
||||
:aria-pressed="layout === 'masonry'"
|
||||
aria-label="瀑布流布局"
|
||||
title="瀑布流布局"
|
||||
@click="layout = 'masonry'"
|
||||
>
|
||||
<template #icon><Columns3 :size="16" /></template>
|
||||
</NButton>
|
||||
</div>
|
||||
<!-- 高频图库操作与显示方式连续排列;纯图标按钮通过 Popover 解释用途。 -->
|
||||
<NPopover trigger="hover" placement="top">
|
||||
</template>
|
||||
瀑布流布局
|
||||
</NPopover>
|
||||
<NPopover trigger="hover" placement="top" :disabled="isMobile">
|
||||
<template #trigger>
|
||||
<NButton
|
||||
class="icon-button"
|
||||
@@ -614,7 +630,7 @@ async function showStaleForms() {
|
||||
</template>
|
||||
刷新图库
|
||||
</NPopover>
|
||||
<NPopover trigger="hover" placement="top">
|
||||
<NPopover trigger="hover" placement="top" :disabled="isMobile">
|
||||
<template #trigger>
|
||||
<NButton
|
||||
class="icon-button"
|
||||
@@ -628,12 +644,15 @@ async function showStaleForms() {
|
||||
<template #icon><Settings2 :size="16" /></template>
|
||||
</NButton>
|
||||
</template>
|
||||
{{ session.receipt || session.promptReceipt ? '批量生图(有回执)' : '批量生图' }}
|
||||
{{
|
||||
session.receipt || session.promptReceipt ? '批量生图(有回执)' : '批量生图'
|
||||
}}
|
||||
</NPopover>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="showImpactDetails" class="asset-impact-context" aria-label="镜头与素材定位">
|
||||
<p v-if="sourceMissing" class="text-danger">来源镜头不存在或不属于当前项目,未读取其素材。</p>
|
||||
<p v-if="sourceCurrent" class="text-xs">
|
||||
@@ -920,7 +939,7 @@ async function showStaleForms() {
|
||||
@apply flex items-center justify-self-end gap-3;
|
||||
}
|
||||
.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) {
|
||||
|
||||
@@ -186,9 +186,13 @@ describe('全站平面主题', () => {
|
||||
const source = readFileSync('src/features/subject-images/SubjectImagesPage.vue', 'utf8')
|
||||
expect(source).not.toContain('class="toolbar-summary"')
|
||||
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(/data-workspace-tools\s+aria-label="批量生图"/)
|
||||
expect(source).not.toContain('title="网格布局"')
|
||||
expect(source).not.toContain('title="瀑布流布局"')
|
||||
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\);/
|
||||
)
|
||||
@@ -209,6 +213,9 @@ describe('全站平面主题', () => {
|
||||
expect(css).toMatch(
|
||||
/\.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('单图标按钮以当前控件高度为边长,尺寸与内容按钮互不影响', () => {
|
||||
|
||||
@@ -143,6 +143,12 @@ describe('过期首帧到具体素材定位', () => {
|
||||
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()).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')
|
||||
expect(wrapper!.get('.form-image-masonry').element).toBe(grid)
|
||||
expect(wrapper!.get('[aria-label="瀑布流布局"]').attributes('aria-pressed')).toBe('true')
|
||||
|
||||
Reference in New Issue
Block a user