From a2c7f231078464522a8df29148a58ddbebf6539c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E8=BF=9B?= Date: Fri, 24 Jul 2026 16:36:51 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E9=80=9A=E7=94=A8=E5=8C=96=20slide?= =?UTF-8?q?=20=E8=AF=86=E5=88=AB=E5=92=8C=E6=B8=B8=E7=A6=BB=E8=8A=82?= =?UTF-8?q?=E7=82=B9=E6=94=B6=E9=9B=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - findSlides(): 不靠命名,靠结构识别(面积/children/已知模式) - collectOrphans(): 用 slide 子树 id 集合判断,不靠名字前缀 - 先递归子节点,子节点有 slide 则父节点是容器 - 7月汇报(6 slide) 和 A2A(7 slide) 都正确识别 --- convert-w2p.js | 72 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 56 insertions(+), 16 deletions(-) diff --git a/convert-w2p.js b/convert-w2p.js index 791278e..411e204 100644 --- a/convert-w2p.js +++ b/convert-w2p.js @@ -318,23 +318,58 @@ function convert(input) { } return results; } - - // 找到所有 slide 容器(name 为 slide-N 或 page 的节点) - function findSlides(node) { - if (!node || !node.name) return []; + // ===== 通用 slide 识别 ===== + // 收集 slide 容器子树的所有 id(用于游离节点判断) + function collectIds(node, set) { + if (!node) return; + if (node.id) set.add(node.id); + if (node.children) for (const c of node.children) collectIds(c, set); + } + + // 已知 slide 命名模式 + const SLIDE_NAME_RE = /^(slide[- ]?|page)/i; + // 结构性节点(不应作为 slide 容器) + const STRUCTURAL_TAGS = new Set(['HTML', 'BODY', 'HEAD', 'CANVAS']); + + function isSlideCandidate(node, canvasArea) { + if (!node || node.type !== 'FRAME') return false; + if (!node.rect) return false; + // 排除截图底图 + if (node.layerGroup === 'comparison') return false; + // 排除结构性节点 + if (STRUCTURAL_TAGS.has(node.tag)) return false; + // 必须有 children + if (!node.children || node.children.length === 0) return false; + // 已知命名模式 + if (node.name && SLIDE_NAME_RE.test(node.name.toLowerCase())) return true; + // 结构特征:面积 > 50% canvas + const rw = node.rect.width ?? node.rect.w ?? 0; + const rh = node.rect.height ?? node.rect.h ?? 0; + const area = rw * rh; + if (canvasArea > 0 && area > canvasArea * 0.5) return true; + return false; + } + + function findSlides(node, canvasArea) { + if (!node) return []; const results = []; - if (/^slide-?\d*$/.test(node.name) || node.name === 'page' || node.name.startsWith('slide ')) { - results.push(node); - } + // 先递归收集子节点中的 slide candidates if (node.children && Array.isArray(node.children)) { for (const child of node.children) { - results.push(...findSlides(child)); + results.push(...findSlides(child, canvasArea)); } } + // 如果子节点中已有 slide candidates,当前节点是容器,不是 slide + if (results.length > 0) return results; + // 当前节点是 slide candidate + if (isSlideCandidate(node, canvasArea)) { + results.push(node); + } return results; } - - const slideContainers = findSlides(inputRoot); + + const canvasArea = (canvas.width || 0) * (canvas.height || 0); + const slideContainers = findSlides(inputRoot, canvasArea); console.log(`找到 ${slideContainers.length} 个 slide 容器`); const slideGroups = slideContainers.map(s => ({ @@ -352,16 +387,21 @@ function convert(input) { slideGroups.forEach(sg => console.log(` ${sg.name}: ${sg.children.length} 个元素`)); - // ===== fallback: 收集不在 slide 子树内但坐标在 slide 范围内的节点 ===== - // 从根节点收集所有非 slide 节点 + // ===== 通用游离节点收集 ===== + // 构建 slide 容器子树的 id 集合 + const slideSubtreeIds = new Set(); + for (const sc of slideContainers) { + collectIds(sc, slideSubtreeIds); + } + function collectOrphans(node) { if (!node || !node.rect) return []; const results = []; const nr = node.rect; const rw = nr.width ?? nr.w; const rh = nr.height ?? nr.h; - // 跳过 slide 容器本身和 raster - if (!node.name?.startsWith('slide-') && !node.name?.startsWith('slide ') && node.type !== 'RECTANGLE' && node.layerGroup !== 'comparison') { + // 用 id 集合判断:不在任何 slide 子树内的节点才是游离节点 + if (!slideSubtreeIds.has(node.id) && node.type !== 'RECTANGLE' && node.layerGroup !== 'comparison') { results.push({ id: node.id, type: node.type, name: node.name, tag: node.tag, rect: { x: nr.x, y: nr.y, w: rw, h: rh }, @@ -370,8 +410,8 @@ function convert(input) { }); } if (node.children) for (const c of node.children) { - // 跳过 slide 容器的子树(它们已经被 collectSlideChildren 处理了) - if (c.name?.startsWith('slide-') || c.name?.startsWith('slide ') || c.name === 'page') continue; + // 跳过已在 slide 子树内的节点 + if (slideSubtreeIds.has(c.id)) continue; results.push(...collectOrphans(c)); } return results;