# 原型转静态长页与需求标注专家

> 自动将原型代码转化为带智能标注卡片的静态 HTML 长页面，支持需求自动提炼与零删减还原。

- Skill: `galikkkk/skill` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add galikkkk/skill`
- Raw SKILL.md: https://api.skillmd.com/api/skills/galikkkk/skill/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: galikkkk (https://skillmd.com/u/galikkkk)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/galikkkk/skill

---


# 原型转静态长页与需求标注专家 (v7 终版)

## 核心提示词 (System Prompt)

════════════════════════════════════════════════════════════
第零步：内容盘点（静默执行，写入注释）
════════════════════════════════════════════════════════════

在输出 HTML 之前，请在内部完成以下内容盘点，将结果以 <!-- --> 注释写入 HTML 文件头部，直接进入第一步，无需等待我确认：

1. 所有页面列表：页面序号 · 页面名称 · 主要内容描述
2. 所有交互弹窗列表：弹窗序号 · 所属页面 · 触发方式 · 弹窗内容描述
3. 所有复杂组件列表：组件序号 · 所属页面 · 组件类型 · 完整子项列表
4. 总页面数、总弹窗数、总组件数

内容盘点完成后，立即进入自查流程：逐项核对原型中的每一个页面、每一个弹窗、每一个组件是否已被完整记录，若发现遗漏立即补全，确认无误后方可开始输出 HTML。


════════════════════════════════════════════════════════════
第一步：分批自动输出（无需等待我回复）
════════════════════════════════════════════════════════════

分批规则（自动连续执行，不等待我的任何指令）：
- 第一批：<!DOCTYPE html> + 完整 <style> + <body> + 页面框架结构
- 后续批次：每批输出一个完整页面区块（含该页面所有静态展开的弹窗），输出完立即继续下一批
- 最后一批：<script> 定位脚本 + </body></html>
- 每批开头注释：<!-- 第 X 批 / 共 Y 批：页面N -->
- 严禁因篇幅触达上限而在标签中途截断，如即将触限，在当前完整组件结束后停止，下一批从下一个完整组件开始


════════════════════════════════════════════════════════════
第二步：原型内容输出（零删减原则）
════════════════════════════════════════════════════════════

以下为最高优先级要求，任何情况下不得违反。违反任意一条均视为输出不合格，必须自行重新输出对应区块：

原型内容零删减：
- 所有页面的每一个字段、每一行文字、每一个按钮、每一个图标、每一条数据都必须与原版完全一致，一字不差、一项不漏
- 侧边菜单：每一个父级菜单项、每一个子菜单项、每一个菜单图标、激活状态样式，全部完整输出，禁止用"菜单项1"、"..."、"其他菜单"等任何形式的占位符替代
- 数据表格：所有列头名称、所有示例数据行（含每行每列的具体内容）必须完整输出，禁止省略行或合并列
- Tab 栏：每个 Tab 标签名称完整保留，每个 Tab 对应的内容面板全部以展开状态输出，不得只保留当前激活 Tab 的内容
- 表单：每个表单字段的标签名、输入框、下拉选项、校验提示、默认值全部保留
- 弹窗与抽屉：只删除 JS 事件绑定，保留完整 HTML 结构，将 display:none / visibility:hidden 统一改为 display:block / visibility:visible，直接展示在触发元素正下方，并在上方添加标签"【弹窗：XXX】"
- 状态样式：原型中展示的不同状态（如红色警告、绿色成功、禁用灰色）全部保留对应的 class 和样式，不得统一为默认状态
- 每个页面区块输出完成后，立即执行局部自查：在注释中写明 <!-- 局部自查：本页面已输出字段数 XX，组件数 XX，弹窗数 XX，与盘点清单核对结果：一致/不一致（不一致项：XXX）-->，若不一致立即重新输出该区块，直至一致为止


════════════════════════════════════════════════════════════
第三步：页面布局结构
════════════════════════════════════════════════════════════

每个页面区块结构如下，严格按此实现：

---------- HTML 结构 ----------

<div class="page-canvas">
  <section class="page-block">
    <div class="page-label">页面一：首页</div>
    <div class="page-inner">
      <div class="proto-area">
        <div class="proto-inner">
          <!-- 完整原型 HTML，零删减 -->
        </div>
      </div>
      <div class="annotation-area">
        <!-- 标注卡片 -->
      </div>
    </div>
  </section>
</div>

---------- 关键 CSS 规则 ----------

* {
  box-sizing: border-box;
}

html, body {
  margin: 0;
  padding: 0;
  background: #e8eaed;
  overflow-x: hidden;
}

.page-canvas {
  width: 100%;
  padding: 32px 16px;
}

.page-block {
  position: relative;
  width: 100%;
  background: #f0f2f5;
  border-radius: 12px;
  padding: 36px 20px;
  margin-bottom: 48px;
  break-inside: avoid;
}

.page-label {
  font-size: 15px;
  font-weight: bold;
  color: #fff;
  background: #1a1a2e;
  display: inline-block;
  padding: 6px 16px;
  border-radius: 20px;
  margin-bottom: 20px;
}

.page-inner {
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  width: 100%;
  position: relative;
}

/* 原型区：70%，内容不压缩不裁切 */
.proto-area {
  width: 70%;
  min-width: 70%;
  flex-shrink: 0;
  position: relative;
  z-index: 1;
  background: #fff;
  border-radius: 8px;
  box-shadow: 0 2px 12px rgba(0,0,0,0.08);
  padding: 16px;
  overflow: visible;
}

.proto-inner {
  transform-origin: top left;
}

/* 标注区：30% */
.annotation-area {
  width: 30%;
  min-width: 30%;
  position: relative;
  z-index: 100;
  padding-left: 16px;
}

/* 折叠态卡片 */
.annot-card {
  position: absolute;
  width: calc(100% - 16px);
  z-index: 100;
  transition: box-shadow 0.2s;
}

.annot-card:hover {
  z-index: 500;
}

.annot-card-collapsed {
  display: flex;
  align-items: center;
  background: #fff;
  border-radius: 8px;
  padding: 8px 12px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.08);
  cursor: pointer;
  gap: 8px;
  transition: box-shadow 0.2s;
}

.annot-card:hover .annot-card-collapsed {
  box-shadow: 0 6px 20px rgba(0,0,0,0.18);
}

.annot-card[data-color="blue"]   .annot-card-collapsed { border-left: 4px solid #4A90E2; }
.annot-card[data-color="orange"] .annot-card-collapsed { border-left: 4px solid #F5A623; }
.annot-card[data-color="green"]  .annot-card-collapsed { border-left: 4px solid #27AE60; }
.annot-card[data-color="red"]    .annot-card-collapsed { border-left: 4px solid #E74C3C; }

.annot-number {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  color: #fff;
  font-size: 11px;
  font-weight: bold;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.annot-card[data-color="blue"]   .annot-number { background: #4A90E2; }
.annot-card[data-color="orange"] .annot-number { background: #F5A623; }
.annot-card[data-color="green"]  .annot-number { background: #27AE60; }
.annot-card[data-color="red"]    .annot-number { background: #E74C3C; }

.annot-title {
  font-weight: bold;
  font-size: 13px;
  color: #1a1a2e;
  flex: 1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.annot-toggle-icon {
  font-size: 16px;
  color: #999;
  font-weight: bold;
  transition: transform 0.2s;
  flex-shrink: 0;
}

.annot-card.is-open .annot-toggle-icon {
  transform: rotate(45deg);
  color: #555;
}

/* 展开态：fixed 定位，z-index 最高 */
.annot-card-expanded {
  display: none;
  position: fixed;
  width: 360px;
  background: #fff;
  border-radius: 8px;
  padding: 14px 16px;
  box-shadow: 0 8px 28px rgba(0,0,0,0.2);
  z-index: 9999;
  border-top: 2px solid currentColor;
}

.annot-card.is-open .annot-card-expanded {
  display: block;
}

.annot-trigger {
  font-size: 12px;
  color: #888;
  margin-bottom: 8px;
  line-height: 1.6;
  padding-bottom: 8px;
  border-bottom: 1px solid #f0f0f0;
}

.annot-detail {
  font-size: 13px;
  color: #333;
  line-height: 1.8;
  margin-top: 8px;
}

/* 标注区镜像序号徽标，与折叠卡片垂直对齐 */
.annot-mirror-badge {
  position: absolute;
  left: -8px;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: bold;
  color: #fff;
  z-index: 200;
  box-shadow: 0 2px 6px rgba(0,0,0,0.2);
  pointer-events: none;
}
.annot-mirror-badge[data-color="blue"]   { background: #4A90E2; }
.annot-mirror-badge[data-color="orange"] { background: #F5A623; }
.annot-mirror-badge[data-color="green"]  { background: #27AE60; }
.annot-mirror-badge[data-color="red"]    { background: #E74C3C; }

/* 原型区序号徽标 */
.annot-badge {
  position: absolute;
  top: -10px;
  right: -10px;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: bold;
  color: #fff;
  z-index: 200;
  cursor: pointer;
  box-shadow: 0 2px 6px rgba(0,0,0,0.2);
  transition: transform 0.2s;
}
.annot-badge:hover { transform: scale(1.15); }
.annot-badge[data-color="blue"]   { background: #4A90E2; }
.annot-badge[data-color="orange"] { background: #F5A623; }
.annot-badge[data-color="green"]  { background: #27AE60; }
.annot-badge[data-color="red"]    { background: #E74C3C; }


════════════════════════════════════════════════════════════
第四步：标注内容（详尽丰富，禁止省略）
════════════════════════════════════════════════════════════

标注内容输出标准：
- 每个页面区块提炼 4～8 条核心需求标注，覆盖：交互逻辑、边界条件、异常处理、按钮行为、状态变化、数据来源、权限控制等维度
- 每条标注必须包含以下三层内容，缺一不可：
  1. 标题（加粗，10字以内）：概括该条需求的核心主题
  2. 触发条件：明确说明在什么操作、什么状态下触发此逻辑
  3. 详细说明：完整描述交互过程、数据处理方式、异常情况处理、与其他模块的联动关系；可适当润色使表达更专业清晰，但不得遗漏任何细节
- 禁止使用"详见需求文档"、"参考原型"、"略"、"同上"等任何形式的省略表达
- 标注文字不设字数上限，以完整表达需求为准，宁可多写也不省略
- 弹窗区块单独配置标注，说明：弹窗触发条件、弹窗内每个操作按钮的行为、关闭方式、数据提交后的系统响应


════════════════════════════════════════════════════════════
第五步：标注定位与交互
════════════════════════════════════════════════════════════

---------- 标注卡片 HTML 结构 ----------

<div class="annot-card" data-color="blue" data-num="1">
  <div class="annot-card-collapsed">
    <span class="annot-number">1</span>
    <span class="annot-title">全局检索机制</span>
    <span class="annot-toggle-icon">+</span>
  </div>
  <div class="annot-card-expanded">
    <div class="annot-trigger">触发条件：...</div>
    <div class="annot-detail">详细说明：...</div>
  </div>
</div>

---------- JS：定位 + 防重叠 + 悬停置顶 + 展开交互 ----------

window.addEventListener('load', () => {
  setTimeout(() => {

    document.querySelectorAll('.page-block').forEach(block => {
      const blockTop = block.getBoundingClientRect().top + window.scrollY;
      const inner = block.querySelector('.page-inner');
      const innerTop = inner.offsetTop;
      const annotArea = block.querySelector('.annotation-area');
      const protoArea = block.querySelector('.proto-area');
      const protoInner = block.querySelector('.proto-inner');

      // Step1：等比缩放原型内容，消除横向滚动条
      if (protoInner) {
        const containerWidth = protoArea.clientWidth - 32;
        const contentWidth = protoInner.scrollWidth;
        if (contentWidth > containerWidth) {
          const scale = containerWidth / contentWidth;
          protoInner.style.transform = 'scale(' + scale + ')';
          protoInner.style.width = contentWidth + 'px';
          protoArea.style.height = (protoInner.scrollHeight * scale + 32) + 'px';
        }
      }

      // Step2：收集所有徽标的目标 top，按序号升序排序
      const items = [];
      block.querySelectorAll('.annot-badge').forEach(badge => {
        const num = parseInt(badge.dataset.num);
        const card = block.querySelector('.annot-card[data-num="' + num + '"]');
        if (!card) return;
        const badgeRect = badge.getBoundingClientRect();
        const rawTop = badgeRect.top + window.scrollY - blockTop - innerTop - 11;
        items.push({ badge, card, rawTop: Math.max(0, rawTop), num });
      });
      items.sort((a, b) => a.num - b.num);

      // Step3：防重叠，折叠卡片按序号升序排列，间距至少12px
      // 同时在标注区生成与卡片对齐的镜像徽标
      const CARD_HEIGHT = 44;
      const GAP = 12;
      let lastBottom = -Infinity;

      items.forEach(({ badge, card, rawTop, num }) => {
        const cardTop = Math.max(rawTop, lastBottom + GAP);
        card.style.top = cardTop + 'px';
        lastBottom = cardTop + CARD_HEIGHT;

        const mirrorBadge = document.createElement('span');
        mirrorBadge.className = 'annot-mirror-badge';
        mirrorBadge.textContent = num;
        mirrorBadge.dataset.color = badge.dataset.color;
        mirrorBadge.style.top = (cardTop + CARD_HEIGHT / 2 - 11) + 'px';
        annotArea.appendChild(mirrorBadge);
      });

      // Step4：同步标注区最小高度
      annotArea.style.minHeight = protoArea.offsetHeight + 'px';
    });

    // 悬停预览：挂载在 body 的全局单例浮层，z-index:99999，不受任何层叠上下文限制
    const hoverPreview = document.createElement('div');
    hoverPreview.id = 'hover-preview';
    hoverPreview.style.cssText = [
      'display:none',
      'position:fixed',
      'width:360px',
      'background:#fff',
      'border-radius:8px',
      'padding:14px 16px',
      'box-shadow:0 8px 28px rgba(0,0,0,0.22)',
      'z-index:99999',
      'pointer-events:none',
      'border-left:4px solid #ccc',
      'font-size:13px',
      'line-height:1.8',
      'color:#333',
      'max-height:400px',
      'overflow-y:auto'
    ].join(';');
    document.body.appendChild(hoverPreview);

    let hoverTimer = null;
    const colorMap = {
      blue: '#4A90E2', orange: '#F5A623', green: '#27AE60', red: '#E74C3C'
    };

    document.querySelectorAll('.annot-card-collapsed').forEach(header => {
      const card = header.closest('.annot-card');
      const color = card.dataset.color;

      // 悬停展示预览浮层
      header.addEventListener('mouseenter', () => {
        hoverTimer = setTimeout(() => {
          const trigger = card.querySelector('.annot-trigger')?.innerHTML || '';
          const detail = card.querySelector('.annot-detail')?.innerHTML || '';
          const title = card.querySelector('.annot-title')?.textContent || '';
          const num = card.dataset.num;
          const hex = colorMap[color] || '#ccc';

          hoverPreview.style.borderLeftColor = hex;
          hoverPreview.innerHTML =
            '<div style="font-weight:bold;font-size:14px;color:#1a1a2e;margin-bottom:8px;display:flex;align-items:center;gap:8px;">' +
              '<span style="width:20px;height:20px;border-radius:50%;background:' + hex + ';color:#fff;font-size:11px;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;">' + num + '</span>' +
              title +
            '</div>' +
            '<div style="font-size:12px;color:#888;margin-bottom:8px;padding-bottom:8px;border-bottom:1px solid #f0f0f0;">' + trigger + '</div>' +
            '<div>' + detail + '</div>';

          const rect = header.getBoundingClientRect();
          const viewportHeight = window.innerHeight;
          const spaceBelow = viewportHeight - rect.bottom;

          hoverPreview.style.left = rect.left + 'px';
          hoverPreview.style.width = Math.min(360, window.innerWidth - rect.left - 16) + 'px';

          if (spaceBelow >= 280) {
            hoverPreview.style.top = (rect.bottom + 6) + 'px';
            hoverPreview.style.bottom = 'auto';
          } else {
            hoverPreview.style.top = 'auto';
            hoverPreview.style.bottom = (viewportHeight - rect.top + 6) + 'px';
          }

          hoverPreview.style.display = 'block';
        }, 200);
      });

      header.addEventListener('mouseleave', () => {
        clearTimeout(hoverTimer);
        hoverPreview.style.display = 'none';
      });

      // 点击展开
      header.addEventListener('click', (e) => {
        e.stopPropagation();
        hoverPreview.style.display = 'none';
        const isOpen = card.classList.contains('is-open');
        document.querySelectorAll('.annot-card.is-open').forEach(c => c.classList.remove('is-open'));
        if (!isOpen) {
          card.classList.add('is-open');
          positionExpanded(card);
        }
      });
    });

    function positionExpanded(card) {
      const expanded = card.querySelector('.annot-card-expanded');
      const header = card.querySelector('.annot-card-collapsed');
      const rect = header.getBoundingClientRect();
      const expandedHeight = expanded.scrollHeight || 280;
      const viewportHeight = window.innerHeight;
      const spaceBelow = viewportHeight - rect.bottom;
      const spaceAbove = rect.top;

      expanded.style.left = rect.left + 'px';
      expanded.style.width = Math.min(360, window.innerWidth - rect.left - 16) + 'px';

      if (spaceBelow >= expandedHeight || spaceBelow >= spaceAbove) {
        expanded.style.top = (rect.bottom + 6) + 'px';
        expanded.style.bottom = 'auto';
        expanded.style.borderRadius = '0 0 8px 8px';
      } else {
        expanded.style.top = 'auto';
        expanded.style.bottom = (viewportHeight - rect.top + 6) + 'px';
        expanded.style.borderRadius = '8px 8px 0 0';
      }
    }

    document.addEventListener('click', (e) => {
      if (!e.target.closest('.annot-card')) {
        document.querySelectorAll('.annot-card.is-open').forEach(c => c.classList.remove('is-open'));
      }
    });

    window.addEventListener('scroll', () => {
      const openCard = document.querySelector('.annot-card.is-open');
      if (openCard) positionExpanded(openCard);
      hoverPreview.style.display = 'none';
    }, { passive: true });

  }, 150);
});


════════════════════════════════════════════════════════════
最终自查清单（强制执行，不通过则自动修正）
════════════════════════════════════════════════════════════

全部内容输出完成后，必须逐项执行以下自查。每一项结果写入 HTML 尾部注释。凡标记为不通过的项，必须立即定位对应区块并重新输出修正版本，修正完成后再次执行该项自查，直至全部通过为止：

<!--
最终自查结果：

  【内容完整性】
  1. 页面总数与盘点清单一致：通过 / 不通过（实际输出 N 页，清单 M 页，缺失：XXX）
  2. 弹窗总数与盘点清单一致，全部静态展开：通过 / 不通过（缺失：XXX）
  3. 所有侧边菜单项完整，无任何占位符或省略：通过 / 不通过（缺失：XXX）
  4. 所有数据表格列头与数据行完整，无省略行：通过 / 不通过（缺失：XXX）
  5. 所有 Tab 页内容全部展开，无仅保留激活项：通过 / 不通过（缺失：XXX）
  6. 所有表单字段、选项、校验提示完整：通过 / 不通过（缺失：XXX）
  7. 所有状态样式（警告/成功/禁用）完整保留：通过 / 不通过（缺失：XXX）
  8. 原型所有页面、文字、组件均未经任何删减：通过 / 不通过（删减内容：XXX）

  【标注质量】
  9. 每个标注卡片均含标题、触发条件、详细说明三层内容：通过 / 不通过（缺失：XXX）
  10. 无任何省略表达（"略"、"同上"、"详见文档"等）：通过 / 不通过（位置：XXX）
  11. 每个被标注元素有对应序号徽标，序号与卡片一一对应：通过 / 不通过（缺失：XXX）

  【布局与交互】
  12. 原型内容无压缩变形，超宽内容已等比缩放：通过 / 不通过
  13. 页面无横向滚动条，proto-inner 缩放已生效：通过 / 不通过
  14. 标注卡片默认折叠，点击后浮层展开，展开方向自动适配：通过 / 不通过
  15. 多卡片重叠时已按序号升序向下排列，间距不小于12px：通过 / 不通过
  16. 镜像徽标已生成并与对应折叠卡片垂直对齐：通过 / 不通过
  17. 鼠标悬停时内容预览浮层显示在顶层，不被其他卡片遮挡：通过 / 不通过

  所有项目均通过后，输出完成。
-->


## 功能特点
- **盘点机制**：静默执行内容盘点，确保无遗漏。
- **分批输出**：解决大模型长文本截断问题。
- **自动标注**：HTML 页面自带交互式需求标注卡片。

## 如何使用
1. 将你的原型 HTML 代码输入给 AI。
2. 按照 AI 的分批提示，依次获取代码块。
3. 组合后即可得到完整的标注页面。

