From 304d6806f0cba48f1d10e519316a9c4fec474b8d Mon Sep 17 00:00:00 2001 From: kdletters Date: Sun, 14 Jun 2026 00:50:05 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8A=BD=E5=8F=96=E9=A1=B9=E7=9B=AE=E9=A1=B5?= =?UTF-8?q?=E6=B5=AE=E5=B1=82=E8=8F=9C=E5=8D=95=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 PlatformFloatingMenu 与菜单项测试 项目卡片更多菜单复用平台浮层菜单 更新 TRACKING 记录组件收口验证 --- TRACKING.md | 1 + .../common/PlatformFloatingMenu.test.tsx | 31 +++++++++++ .../common/PlatformFloatingMenu.tsx | 54 +++++++++++++++++++ src/components/project/ProjectGalleryView.tsx | 28 +++++----- src/index.css | 6 +-- 5 files changed, 103 insertions(+), 17 deletions(-) create mode 100644 src/components/common/PlatformFloatingMenu.test.tsx create mode 100644 src/components/common/PlatformFloatingMenu.tsx diff --git a/TRACKING.md b/TRACKING.md index c30c12cc..7e89772e 100644 --- a/TRACKING.md +++ b/TRACKING.md @@ -75,3 +75,4 @@ - 2026-06-13 路由与数据归属修正:图片画布页面路由改为 `/editor/canvas`;新增 `editor_canvas` 表作为 project 下的画布数据表,当前工程创建时同步创建默认画布,保存 layout 时写入默认画布,API 响应同时返回 `project.canvas` 和兼容顶层 `viewport/layers`。 - 2026-06-13 项目页修正:新增 `/project` 作为图片画布工程列表入口;从“我的”页进入项目页,项目卡片进入 `/editor/canvas?projectid=`,并补齐项目列表、重命名、删除和批量删除 API。`GET /api/editor/projects` 在重启后的 api-server 上返回未登录 401,不再是旧进程的 405;`/project` 前端路由 smoke 可渲染项目页白底布局。 - 2026-06-14 组件复用修正:项目页重命名弹窗改为复用 `UnifiedModal`、`PlatformTextField` 和 `PlatformActionButton`,删除项目页局部 modal / input 样式,避免同类弹窗和表单 chrome 重复实现。 +- 2026-06-14 组件复用修正:新增 `PlatformFloatingMenu` / `PlatformFloatingMenuItem`,项目卡片右下角更多菜单改为复用平台浮层菜单原语;验证命令:`npm run test -- src/components/common/PlatformFloatingMenu.test.tsx src/components/project/ProjectGalleryView.test.tsx`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。 diff --git a/src/components/common/PlatformFloatingMenu.test.tsx b/src/components/common/PlatformFloatingMenu.test.tsx new file mode 100644 index 00000000..37ca5cca --- /dev/null +++ b/src/components/common/PlatformFloatingMenu.test.tsx @@ -0,0 +1,31 @@ +/* @vitest-environment jsdom */ + +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { describe, expect, it, vi } from 'vitest'; + +import { + PlatformFloatingMenu, + PlatformFloatingMenuItem, +} from './PlatformFloatingMenu'; + +describe('PlatformFloatingMenu', () => { + it('renders menu items with accessible menu semantics', async () => { + const onRename = vi.fn(); + const user = userEvent.setup(); + + render( + + + 重命名 + + , + ); + + expect(screen.getByRole('menu')).toBeTruthy(); + + await user.click(screen.getByRole('menuitem', { name: '重命名' })); + + expect(onRename).toHaveBeenCalledOnce(); + }); +}); diff --git a/src/components/common/PlatformFloatingMenu.tsx b/src/components/common/PlatformFloatingMenu.tsx new file mode 100644 index 00000000..e8e85b80 --- /dev/null +++ b/src/components/common/PlatformFloatingMenu.tsx @@ -0,0 +1,54 @@ +import type { ButtonHTMLAttributes, ReactNode } from 'react'; + +type PlatformFloatingMenuProps = { + children: ReactNode; + className?: string; +}; + +type PlatformFloatingMenuItemProps = Omit< + ButtonHTMLAttributes, + 'children' +> & { + icon?: ReactNode; + children: ReactNode; +}; + +/** + * 平台浮层菜单原语。 + * 用于卡片角标、画布局部菜单等轻量动作集合,统一 role 与菜单项 chrome。 + */ +export function PlatformFloatingMenu({ + children, + className, +}: PlatformFloatingMenuProps) { + return ( +
+ {children} +
+ ); +} + +export function PlatformFloatingMenuItem({ + icon, + children, + className, + type = 'button', + ...buttonProps +}: PlatformFloatingMenuItemProps) { + return ( + + ); +} diff --git a/src/components/project/ProjectGalleryView.tsx b/src/components/project/ProjectGalleryView.tsx index 2cf14d0f..f1ef18b2 100644 --- a/src/components/project/ProjectGalleryView.tsx +++ b/src/components/project/ProjectGalleryView.tsx @@ -18,6 +18,10 @@ import { } from '../../services/image-editor/editorProjectClient'; import { PlatformActionButton } from '../common/PlatformActionButton'; import { PlatformEmptyState } from '../common/PlatformEmptyState'; +import { + PlatformFloatingMenu, + PlatformFloatingMenuItem, +} from '../common/PlatformFloatingMenu'; import { PlatformIconButton } from '../common/PlatformIconButton'; import { PlatformTextField } from '../common/PlatformTextField'; import { UnifiedModal } from '../common/UnifiedModal'; @@ -242,10 +246,9 @@ export function ProjectGalleryView({ onOpenProject }: ProjectGalleryViewProps) { }} /> {activeMenuProjectId === project.projectId ? ( -
- - -
+ 删除 + + ) : null} ) : null} diff --git a/src/index.css b/src/index.css index 3aa5b1ce..076df58f 100644 --- a/src/index.css +++ b/src/index.css @@ -3126,7 +3126,7 @@ html[data-mobile-keyboard-open='true'] .platform-mobile-bottom-dock { opacity: 1; } -.project-gallery__card-menu { +.platform-floating-menu { position: absolute; right: 0; bottom: 2.4rem; @@ -3139,7 +3139,7 @@ html[data-mobile-keyboard-open='true'] .platform-mobile-bottom-dock { box-shadow: 0 18px 40px rgba(15, 23, 42, 0.16); } -.project-gallery__card-menu button { +.platform-floating-menu__item { display: flex; align-items: center; gap: 0.45rem; @@ -3152,7 +3152,7 @@ html[data-mobile-keyboard-open='true'] .platform-mobile-bottom-dock { text-align: left; } -.project-gallery__card-menu button:hover { +.platform-floating-menu__item:hover { background: #f3f4f6; }