diff --git a/design-qa.md b/design-qa.md index a633b81..068ca16 100644 --- a/design-qa.md +++ b/design-qa.md @@ -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 diff --git a/src/features/subject-images/SubjectImagesPage.vue b/src/features/subject-images/SubjectImagesPage.vue index d0241a6..498f04e 100644 --- a/src/features/subject-images/SubjectImagesPage.vue +++ b/src/features/subject-images/SubjectImagesPage.vue @@ -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,68 +577,78 @@ async function showStaleForms() { />