0 ? " has-issues" : ""}`}>
- {issues.length > 0 ? (
-
- ) : (
-
- )}
+ {issues.length > 0 ? : }
问题:{issues.length}
{issues.length === 0 ? (
diff --git a/modules/sdk/@sfmc-sdk/ui-studio-web/src/components/DragHandle.tsx b/modules/sdk/@sfmc-sdk/ui-studio-web/src/components/DragHandle.tsx
index 5eafb57b..0234e482 100644
--- a/modules/sdk/@sfmc-sdk/ui-studio-web/src/components/DragHandle.tsx
+++ b/modules/sdk/@sfmc-sdk/ui-studio-web/src/components/DragHandle.tsx
@@ -6,8 +6,8 @@
* 当前用于组件库条目与组件树节点。
*/
-import { GripVertical } from "lucide-react";
import type { DragEvent } from "react";
+import { GripVertical } from "./icons";
/** 侧栏六点抓手:仅此元素发起 HTML5 拖放。 */
export function DragHandle({
@@ -34,12 +34,12 @@ export function DragHandle({
event.dataTransfer.setDragImage(
ghost,
Math.max(0, event.clientX - rect.left),
- Math.max(0, event.clientY - rect.top),
+ Math.max(0, event.clientY - rect.top)
);
}
}}
>
-
+
);
}
diff --git a/modules/sdk/@sfmc-sdk/ui-studio-web/src/components/HeaderMenu.tsx b/modules/sdk/@sfmc-sdk/ui-studio-web/src/components/HeaderMenu.tsx
index d7b8e152..baec1f43 100644
--- a/modules/sdk/@sfmc-sdk/ui-studio-web/src/components/HeaderMenu.tsx
+++ b/modules/sdk/@sfmc-sdk/ui-studio-web/src/components/HeaderMenu.tsx
@@ -1,18 +1,11 @@
-/**
- * HeaderMenu.tsx — 顶栏下拉菜单(基于 Headless UI Menu)。
- *
- * MenuItems 通过 anchor 属性由 floating-ui 定位到按钮下方;
- * 焦点/禁用状态经 data-focus / data-disabled 属性注入,样式在 styles.css 中定义。
- */
+/** 顶栏数据驱动菜单,复用 desktop 的 Base UI 菜单与 SnowUI 浮层。 */
+import { Button } from "@sfmc-bds/ui/controls";
+import { Menu, type MenuEntry } from "@sfmc-bds/ui/overlays";
+import type { UiIcon } from "./icons";
-import { Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/react";
-import type { LucideIcon } from "lucide-react";
-
-/** 菜单项;"separator" 表示分隔线。 */
export interface HeaderMenuItem {
- icon?: LucideIcon;
+ icon?: UiIcon;
label: string;
- /** 右侧展示的快捷键提示(仅展示,不绑定)。 */
shortcut?: string;
danger?: boolean;
disabled?: boolean;
@@ -20,38 +13,21 @@ export interface HeaderMenuItem {
}
export type HeaderMenuEntry = HeaderMenuItem | "separator";
-export function HeaderMenu({
- label,
- entries,
-}: {
- label: string;
- entries: HeaderMenuEntry[];
-}) {
- return (
-
+export const menuEntries = (entries: HeaderMenuEntry[]): MenuEntry[] =>
+ entries.map((entry, index) =>
+ entry === "separator"
+ ? { type: "separator", key: String(index) }
+ : {
+ key: String(index),
+ label: entry.label,
+ icon: entry.icon?.iconName,
+ hint: entry.shortcut,
+ danger: entry.danger,
+ disabled: entry.disabled,
+ onSelect: entry.onClick,
+ }
);
+
+export function HeaderMenu({ label, entries }: { label: string; entries: HeaderMenuEntry[] }) {
+ return
);
diff --git a/modules/sdk/@sfmc-sdk/ui-studio-web/src/components/ProjectTree.tsx b/modules/sdk/@sfmc-sdk/ui-studio-web/src/components/ProjectTree.tsx
index 548f257a..b337327c 100644
--- a/modules/sdk/@sfmc-sdk/ui-studio-web/src/components/ProjectTree.tsx
+++ b/modules/sdk/@sfmc-sdk/ui-studio-web/src/components/ProjectTree.tsx
@@ -9,7 +9,20 @@
* - 其他文件:未登记为页面的 JSON(隐藏 .ui-studio/ 元数据)。
*/
+import { useEffect, useRef, useState, type DragEvent, type MouseEvent as ReactMouseEvent } from "react";
import type { UiNode } from "../../../src/contracts/ui-document.js";
+import {
+ FEATURE_FILE,
+ nodeChildSlots,
+ nodeDisplayParts,
+ parseNodeLocation,
+ type InsertAddress,
+ type ProjectView,
+ type Selection,
+} from "../lib/model";
+import { screenRefs } from "../store/project";
+import { ContextMenu, useContextMenu } from "./ContextMenu";
+import { DragHandle } from "./DragHandle";
import {
AppWindow,
ChevronDown,
@@ -22,22 +35,9 @@ import {
Pencil,
Plus,
Trash2,
-} from "lucide-react";
-import { DragHandle } from "./DragHandle";
-import { useEffect, useRef, useState, type DragEvent, type MouseEvent as ReactMouseEvent } from "react";
+} from "./icons";
import { nodeTypeIcon } from "./node-icons";
-import {
- FEATURE_FILE,
- nodeChildSlots,
- nodeDisplayParts,
- parseNodeLocation,
- type InsertAddress,
- type ProjectView,
- type Selection,
-} from "../model";
import { MOVE_MIME } from "./Palette";
-import { screenRefs } from "../store/project";
-import { ContextMenu, useContextMenu } from "./ContextMenu";
interface ProjectTreeProps {
view: ProjectView;
@@ -69,12 +69,7 @@ export function ProjectTree({
const declared = new Set(screenRefs(view.files).map((ref) => ref.file));
// .ui-studio/ 为 Studio 元数据(含旧场景 fixture),不在树里展示。
const otherFiles = Object.keys(view.files)
- .filter(
- (file) =>
- file !== FEATURE_FILE &&
- !file.startsWith(".ui-studio/") &&
- !declared.has(file),
- )
+ .filter((file) => file !== FEATURE_FILE && !file.startsWith(".ui-studio/") && !declared.has(file))
.sort();
// 右键菜单状态:页面行与其他文件行共用。
const { menu, open: openMenu, close: closeMenu } = useContextMenu();
@@ -111,9 +106,7 @@ export function ProjectTree({
event.dataTransfer.dropEffect = "none";
return;
}
- setDropHint((prev) =>
- prev?.path === hint.path && prev.pos === hint.pos ? prev : hint,
- );
+ setDropHint((prev) => (prev?.path === hint.path && prev.pos === hint.pos ? prev : hint));
event.dataTransfer.dropEffect = "move";
},
drop(event, path, node) {
@@ -137,9 +130,7 @@ export function ProjectTree({
// 从画布选中嵌套节点时,自动展开其祖先,避免折叠后找不到当前项。
useEffect(() => {
if (selection?.kind !== "screen" || !selection.nodePath) return;
- const keys = ancestorNodePaths(selection.nodePath).map(
- (path) => `${selection.screenId}:${path}`,
- );
+ const keys = ancestorNodePaths(selection.nodePath).map((path) => `${selection.screenId}:${path}`);
setCollapsed((prev) => {
let changed = false;
const next = new Set(prev);
@@ -171,9 +162,7 @@ export function ProjectTree({