prototype-design

通用页面原型设计技能。基于 58种设计风格(默认Figma 风格) UI 的复杂业务系统 HTML 原型开发, 包括标准页面结构、统计卡片、筛选条件、数据表格、弹窗设计。 适用场景:(1) 创建新的管理页面原型 (2) 对照业务文档实现功能模块 (3) 设计弹窗和详情页 (4) 构建带看板和列表视图的页面。 **样式选择**:内置58+设计系统,直接引用 `references/design-systems/` 目录。 使用时指定 DESIGN_SYSTEM=<名称> 即可应用对应设计风格。

Safety Notice

This listing is from the official public ClawHub registry. Review SKILL.md and referenced scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "prototype-design" with this command: npx skills add contsun/prototype-design

原型设计技能

When to Use(何时使用)

  • 用户要求创建新的管理页面原型
  • 需要实现业务文档中的功能模块
  • 需要设计弹窗、详情页
  • 需要构建带看板或列表视图的页面
  • 涉及 HTML/CSS/JS 前端代码的原型开发

How to Use(如何使用)

1. 项目初始化

mkdir -p project/{pages,styles,scripts}
cd project

2. 设计系统选择

内置设计系统(references/design-systems/):

  • 默认:figma-DESIGN.md
  • 管理后台:linear-DESIGN.md
  • 简约专业:vercel-DESIGN.md

原型设计技能

通用页面原型开发指南,支持58+设计系统风格。

项目结构

project/
├── index.html          # 单页应用入口
├── pages/              # 页面模块
│   ├── dashboard.html
│   ├── staff.html
│   └── ...
├── styles/
│   └── main.css        # 全局样式
└── scripts/
    └── main.js        # 全局脚本

设计系统选择

重要:设计系统文件位于 references/design-systems/ 目录。

使用步骤

  1. 确定设计风格:根据项目需求选择设计系统

    • 管理后台常用:Figma、Linear、Notion、Vercel
    • 电商/消费:Airbnb、Spotify、Stripe
    • 企业级:IBM、Salesforce
  2. 读取对应 DESIGN.md

    cat references/design-systems/figma-DESIGN.md
    
  3. 应用设计规范

    • Color Palette → CSS变量
    • Typography → 字体规范
    • Component Stylings → 组件样式

常用设计系统快速参考

风格设计系统特点
默认figma-DESIGN.md多彩色、现代
管理后台linear-DESIGN.md紫色主题、精致
简约专业vercel-DESIGN.md黑白精准、极简
温暖风格notion-DESIGN.md暖色极简
企业级stripe-DESIGN.md紫色渐变、高级感

标准页面结构

<div id="page-xxx" class="page">
  <!-- 1. Header -->
  <header class="header">
    <div class="header-left">
      <h2>页面标题</h2>
      <div class="breadcrumb"><span>首页</span><span>/</span><span>当前路径</span></div>
    </div>
    <div class="header-right">
      <button class="btn btn-outline btn-sm">导出</button>
      <button class="btn btn-primary btn-sm" onclick="openAddModal()">新增</button>
    </div>
  </header>

  <!-- 2. 统计卡片 -->
  <div class="stats-grid" style="grid-template-columns:repeat(4,1fr);">
    <div class="stat-card">
      <div class="stat-card-title">标题</div>
      <div class="stat-card-value">数值</div>
      <div class="stat-card-change">描述</div>
    </div>
  </div>

  <!-- 3. 筛选条件 -->
  <div class="table-filters">
    <div class="filter-group">
      <span class="filter-label">字段名</span>
      <input type="text" class="filter-input" placeholder="提示...">
    </div>
  </div>

  <!-- 4. 数据表格 -->
  <table>
    <thead><tr><th>字段1</th><th>字段2</th><th>操作</th></tr></thead>
    <tbody>
      <tr>
        <td>数据</td>
        <td><span class="tag tag-success">状态</span></td>
        <td><button class="btn btn-sm btn-outline">详情</button></td>
      </tr>
    </tbody>
  </table>
</div>

弹窗设计规范

标准弹窗模板

<!-- 遮罩 + 居中弹窗 -->
<div id="modal-xxx" style="
  display:none;
  position:fixed;
  top:0;left:0;right:0;bottom:0;
  background:rgba(0,0,0,0.6);
  z-index:1000;
  align-items:center;
  justify-content:center;
">
  <!-- 内容框 -->
  <div style="
    background:white;
    border-radius:16px;
    width:560px;
    max-width:90vw;
    max-height:85vh;
    overflow:hidden;
    box-shadow:0 25px 80px rgba(0,0,0,0.35);
  ">
    <!-- 标题栏 -->
    <div style="
      padding:20px 24px;
      border-bottom:1px solid #E5E7EB;
      display:flex;
      align-items:center;
      justify-content:space-between;
      background:#F9FAFB;
    ">
      <h3 style="margin:0;font-size:18px;font-weight:600;color:#111827;">弹窗标题</h3>
      <button onclick="closeModal()" style="
        border:none;
        background:none;
        font-size:24px;
        color:#6B7280;
        cursor:pointer;
        padding:4px;
        line-height:1;
      ">×</button>
    </div>
    <!-- 内容区 -->
    <div style="
      padding:24px;
      overflow-y:auto;
      max-height:calc(85vh - 140px);
    ">
      <!-- 表单项示例 -->
      <div style="display:grid;grid-template-columns:1fr 1fr;gap:20px;">
        <div>
          <label style="display:block;font-size:14px;font-weight:500;margin-bottom:8px;color:#374151;">
            字段名 <span style="color:#EF4444;">*</span>
          </label>
          <select style="
            width:100%;
            padding:10px 12px;
            border:1px solid #E5E7EB;
            border-radius:8px;
            font-size:14px;
            background:white;
          ">
            <option value="">请选择</option>
            <option>选项1</option>
            <option>选项2</option>
          </select>
        </div>
        <div>
          <label style="display:block;font-size:14px;font-weight:500;margin-bottom:8px;color:#374151;">字段名</label>
          <input type="text" placeholder="请输入" style="
            width:100%;
            padding:10px 12px;
            border:1px solid #E5E7EB;
            border-radius:8px;
            font-size:14px;
            box-sizing:border-box;
          ">
        </div>
      </div>
      <div style="margin-top:16px;">
        <label style="display:block;font-size:14px;font-weight:500;margin-bottom:8px;color:#374151;">备注</label>
        <textarea rows="3" placeholder="请输入备注" style="
          width:100%;
          padding:10px 12px;
          border:1px solid #E5E7EB;
          border-radius:8px;
          font-size:14px;
          resize:none;
          box-sizing:border-box;
        "></textarea>
      </div>
    </div>
    <!-- 底部按钮 -->
    <div style="
      padding:16px 24px;
      border-top:1px solid #E5E7EB;
      display:flex;
      justify-content:flex-end;
      gap:12px;
      background:#F9FAFB;
    ">
      <button onclick="closeModal()" style="
        padding:10px 20px;
        border:1px solid #E5E7EB;
        background:white;
        border-radius:8px;
        font-size:14px;
        font-weight:500;
        color:#374151;
        cursor:pointer;
      ">取消</button>
      <button onclick="saveData()" style="
        padding:10px 20px;
        border:none;
        background:#4F46E5;
        color:white;
        border-radius:8px;
        font-size:14px;
        font-weight:500;
        cursor:pointer;
      ">保存</button>
    </div>
  </div>
</div>

弹窗样式要点(必记)

元素样式属性正确值常见错误
外层遮罩positionfixedabsolute 会滚动
遮罩背景backgroundrgba(0,0,0,0.6)0.5 太淡,0.7 太浓
遮罩定位top/left/right/bottom0(全屏覆盖)忘记设置任一边
弹窗容器displayflex父级用 flex 居中
居中方式align-items + justify-contentcenter + center缺少任一属性
内容框圆角border-radius16px12px 不够现代
内容框宽度width560px90vw固定 px 在小屏幕不友好
内容框高度max-height85vh80vh 可能显示不全
阴影box-shadow0 25px 80px rgba(0,0,0,0.35)太淡看不出层次

表单项样式要点

元素样式属性正确值
输入框/下拉框padding10px 12px
输入框/下拉框border-radius8px
输入框/下拉框border1px solid #E5E7EB
输入框/下拉框font-size14px
textarearesizenone
textareabox-sizingborder-box
必填标记color#EF4444 (红色)

⚠️ 常见错误

  1. 不要使用 CSS 类名:如 class="modal"class="btn btn-primary" - 这些类通常没有定义样式或样式被覆盖
  2. 必须使用内联样式:弹窗组件应完全使用内联样式,避免外部 CSS 干扰
  3. 遮罩层必须有 z-index:1000:确保在最上层
  4. 内容框不能用 overflow:hidden:内容超出需要滚动,必须用 overflow-y:auto

看板视图

<div style="display:grid;grid-template-columns:repeat(5,1fr);gap:16px;">
  <!-- 列 -->
  <div style="background:#F9FAFB;border-radius:12px;padding:16px;">
    <div style="display:flex;justify-content:space-between;padding-bottom:12px;border-bottom:1px solid #E5E7EB;">
      <span>待开始</span>
      <span style="background:#FEE2E2;color:#DC2626;padding:2px 8px;border-radius:10px;font-size:12px;">5</span>
    </div>
    <div style="background:white;border-radius:8px;padding:12px;margin-top:12px;cursor:pointer;" onclick="showDetail()">
      <div style="font-weight:500;">单号</div>
      <div style="font-size:12px;color:#6B7280;">描述</div>
    </div>
  </div>
</div>

Tab 切换

<div class="tabs" style="margin-bottom:24px;">
  <button class="tab active" onclick="switchTab('tab1')" style="background:#EEF2FF;color:#4F46E5;">Tab1</button>
  <button class="tab" onclick="switchTab('tab2')" style="background:#F3F4F6;color:#6B7280;">Tab2</button>
</div>

⚠️ 重要:保持 index.html 同步

问题现象:更新 pages/xxx.html 后,直接打开 index.html 查看不会看到变化。

原因index.html 是单页应用入口,包含所有页面的内嵌副本。pages/ 目录的修改不会自动同步。

🔴 弹窗必须放在页面 div 内部

常见错误:弹窗 HTML 放在 </div> (页面关闭标签) 之后

<!-- ❌ 错误:弹窗在页面 div 外部 -->
<div id="page-xxx" class="page">
  ...页面内容...
</div>
<!-- 弹窗在这是错误的 -->
<div id="modal-xxx" style="display:none...">弹窗内容</div>

<!-- ✅ 正确:弹窗必须在页面 div 内部 -->
<div id="page-xxx" class="page">
  ...页面内容...
  <!-- 弹窗放在这里 -->
  <div id="modal-xxx" style="display:none...">弹窗内容</div>
</div>

🔴 Tab 切换函数必须使用页面级作用域

常见错误:使用全局选择器 .tabs .tab 会影响所有页面

// ❌ 错误:会选择页面上所有的 tab
const tabs = document.querySelectorAll('.tabs .tab');

// ✅ 正确:使用页面级作用域
const tabs = document.querySelectorAll('#page-xxx .tabs > .tab');

Tab 切换函数模板

function switchXxxTab(tabName) {
  // 1. 隐藏所有 tab 内容
  document.querySelectorAll('.xxx-tab').forEach(t => t.style.display = 'none');
  
  // 2. 显示选中的 tab 内容
  document.getElementById('xxx-' + tabName).style.display = 'block';
  
  // 3. 更新 tab 按钮状态(使用页面级作用域)
  const tabs = document.querySelectorAll('#page-xxx .tabs > .tab');
  tabs.forEach((t, i) => {
    if (i === /* 当前tab索引 */) {
      t.style.background = '#EEF2FF';
      t.style.color = '#4F46E5';
    } else {
      t.style.background = '#F3F4F6';
      t.style.color = '#6B7280';
    }
  });
}

✅ 同步脚本

解决方案:每次创建/更新 pages/ 目录的页面后,必须运行以下同步脚本:

cd project
python3 << 'PYEOF'
import os
import re

# 1. 读取当前index.html
with open('index.html', 'r', encoding='utf-8') as f:
    content = f.read()

# 2. 获取pages/目录下所有html文件(按文件名排序)
pages_dir = 'pages'
page_files = sorted([f for f in os.listdir(pages_dir) if f.endswith('.html')])

# 3. 对每个页面文件,提取 <div id="page-xxx" 内容
for page_file in page_files:
    with open(os.path.join(pages_dir, page_file), 'r', encoding='utf-8') as f:
        page_content = f.read()
    
    # 提取 page-xxx 块的完整内容
    match = re.search(r'(<div id="(page-\w+)"[^>]*class="page"[^>]*>.*?<script>\s*function \w+Open\w+Modal)', page_content, re.DOTALL)
    if not match:
        match = re.search(r'(<div id="(page-\w+)"[^>]*class="page"[^>]*>.*?)(<!--\s+<div id="page-)', page_content, re.DOTALL)
    
    if match:
        page_id = match.group(2)
        page_block = match.group(1).strip()
        
        # 在index.html中查找并替换对应的page块
        # 匹配模式:<div id="page-xxx" class="page">...</div> 或 <div id="page-xxx" class="page">...<div id="page-yyy"
        pattern = rf'(<div id="{re.escape(page_id)}"[^>]*class="page"[^>]*>)(.*?)((?=<div id="page-)|(?=<script>\s*$)|(?=</main>)|(?=</body>))'
        
        existing = re.search(pattern, content, re.DOTALL)
        if existing:
            content = content[:existing.start()] + page_block + content[existing.end():]
            print(f'✓ Updated: {page_id}')
        else:
            print(f'⚠ Not found in index: {page_id}')
    else:
        print(f'⚠ No page block found in: {page_file}')

# 4. 写回index.html
with open('index.html', 'w', encoding='utf-8') as f:
    f.write(content)

print('\n✅ Sync complete!')
PYEOF

重要提示

  • ❌ 不要跳过同步步骤
  • ❌ 不要只更新 pages/ 目录就以为完成了
  • ✅ 每次修改后都要运行同步脚本,再验证 index.html

组件命名规范

组件类名
页面容器.page
页头.header
内容区.content
统计卡片.stat-card
表格卡片.table-card
按钮-主要.btn .btn-primary
按钮-次要.btn .btn-outline
标签.tag
状态.status
分页.pagination

重建 index.html

cd project
python3 << 'PYEOF'
page_order = ['dashboard', 'page1', 'page2', ...]
pages = [open(f'pages/{p}.html').read() for p in page_order]
html = f'''<!DOCTYPE html>
<html>
<head>
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700" rel="stylesheet">
  <link rel="stylesheet" href="styles/main.css">
</head>
<body>
  <div class="layout">
    <aside class="sidebar">...</aside>
    <main class="main">
      {chr(10).join(pages)}
    </main>
  </div>
  <script src="scripts/main.js"></script>
</body>
</html>'''
open('index.html','w').write(html)
PYEOF

常见问题速查表

问题症状解决方案
弹窗打不开xxx is not defined检查 main.js 引用位置和函数定义
Tab 选中态错乱所有 Tab 同时选中用页面级作用域选择器
Hash URL 不工作页面 display:none添加 hashchange 监听
表格侵入侧边栏横向滚动时布局乱用 Ant Design 固定表头表格
复选框丢失数据行没有 checkbox手动添加每个数据行的复选框
同步后功能失效弹窗/按钮不工作检查 script 标签格式和 div 平衡

长对话上下文管理(重要)

问题背景

当对话持续较长时,上下文窗口会逐渐积累历史消息,可能导致:

  • 模型响应变慢
  • token 超出限制
  • 早期上下文被遗忘

解决方案:自动压缩 + 分阶段提交

1. 自动 Compaction(对话压缩)

OpenClaw 会自动对长对话进行 compaction(压缩):

  • 将对话历史压缩成一个 summary 摘要
  • 保留关键的项目进展、决策、待办事项
  • 释放大量 token 空间

触发时机:通常在上下文累积到一定量时自动进行,无需手动干预

2. Compaction 后的处理流程

当收到 compaction 后的新对话时,立即执行以下步骤:

1️⃣ 读取 summary 摘要,理解当前项目状态
       ↓
2️⃣ 检查 memory/YYYY-MM-DD.md 文件
       ↓
3️⃣ 将新进展追加到 memory 文件
       ↓
4️⃣ 继续工作,保持文件平衡

3. 分阶段提交策略(小步提交)

原则:每个功能完成后立即提交,避免大量变更堆积

# ✅ 好的做法:功能完成即提交
git add -A && git commit -m "feat: 新增用户管理弹窗"

# ❌ 不好的做法:多个功能一起提交
git add -A && git commit -m "feat: 多项优化"

好处

  • 如果出问题,容易回溯
  • 减少每次提交的文件变更量
  • Compaction 后的 summary 更简洁

4. 保持文件平衡

每次 HTML 修改后检查 div 平衡:

# 检查 div 平衡
python3 -c "
with open('index.html', 'r') as f:
    c = f.read()
print(f'opens={c.count(\"<div\")}, closes={c.count(\"</div>\")}, diff={c.count(\"<div\")-c.count(\"</div>\")}')
"

# ✅ 正确输出:opens=xxx, closes=xxx, diff=0
# ❌ 错误输出:opens=xxx, closes=xxx, diff=≠0

如果 diff 不为 0,立即修复:

  • 缺少 </div>:在合适位置添加
  • 多余 </div>:删除多余的部分

5. 定期同步到 memory 文件

每次 compaction 后或重要里程碑时,将进展写入 memory/YYYY-MM-DD.md

## 14:30 状态更新

### 功能名称 - 完善/修复

**新增内容**:
- 具体修改1
- 具体修改2

**提交记录**:`abc1234` feat: 描述

**文件**:`/path/to/file.html`

6. 长对话工作流

用户提出需求
       ↓
理解现有代码结构(查看 summary + 文件)
       ↓
用 Python 脚本做精确文本替换
       ↓
修改完检查 div 平衡
       ↓
提交代码(每个功能单独提交)
       ↓
截图验证(如需要)
       ↓
回复用户
       ↓
(Compaction 自动触发时)
       ↓
读取 summary,追加进展到 memory 文件

为什么单页 HTML 不受上下文限制影响

WMS 原型是单页 HTML(index.html),3700+ divs:

  • 不是日志文件:不会无限增长
  • 每次修改同一个文件:不是追加模式
  • Compaction 只压缩对话历史:不影响 HTML 文件本身

关键心态

"我是 AI,每次醒来都是新的开始。但文件里的内容是我的记忆。"

  • 上下文超限是 对话历史 被压缩,不是文件内容丢失
  • HTML 文件本身不受影响
  • 只要保持文件平衡和定期 commit,就能稳定工作

相关资源

  • 内置设计系统references/design-systems/ 目录包含58+设计系统完整规范
    • 包含:Figma、Linear、Stripe、Vercel、Notion 等
  • 字段规范field-norms.md - 各模块标准字段定义

修改工作流(重要)

标准修改流程

每次对任何页面进行修改,必须遵循以下步骤:

1️⃣ 修改 pages/xxx.html
       ↓
2️⃣ 运行同步脚本 → 更新 index.html
       ↓
3️⃣ 本地测试 → 验证功能正常
       ↓
4️⃣ 提交代码 → git add + commit

🔴 第一步:修改 pages/xxx.html

pages/ 目录的对应页面文件中进行修改:

  • 弹窗 HTML 必须放在 <div id="page-xxx" class="page"> 内部
  • 脚本函数放在页面的 <script> 块中

🔴 第二步:同步到 index.html

必须运行同步脚本

cd your-project-directory
python3 << 'PYEOF'
import os
import re

with open('index.html', 'r', encoding='utf-8') as f:
    content = f.read()

pages_dir = 'pages'
page_files = sorted([f for f in os.listdir(pages_dir) if f.endswith('.html')])

for page_file in page_files:
    with open(os.path.join(pages_dir, page_file), 'r', encoding='utf-8') as f:
        page_content = f.read()
    
    match = re.search(r'(<div id="(page-\w+)"[^>]*class="page"[^>]*>.*?)(?=<div id="page-|\s*<script>|\s*</main>)', page_content, re.DOTALL)
    if not match:
        print(f'⚠ Skip: {page_file}')
        continue
    
    page_id = match.group(2)
    page_block = match.group(1).strip()
    
    # 验证 div 平衡
    opens = page_block.count('<div')
    closes = page_block.count('</div>')
    if opens != closes:
        print(f'❌ Div imbalance in {page_id}: opens={opens}, closes={closes}')
        continue
    
    pattern = rf'(<div id="{re.escape(page_id)}"[^>]*class="page"[^>]*>)(.*?)(?=<div id="page-|\s*</main>)'
    existing = re.search(pattern, content, re.DOTALL)
    
    if existing:
        content = content[:existing.start()] + page_block + content[existing.end():]
        print(f'✓ Synced: {page_id}')
    else:
        print(f'⚠ Not found in index: {page_id}')

opens_all = content.count('<div')
closes_all = content.count('</div>')
print(f'\nDiv balance: opens={opens_all}, closes={closes_all}, diff={opens_all-closes_all}')

with open('index.html', 'w', encoding='utf-8') as f:
    f.write(content)

print('✅ Sync complete!')
PYEOF

重要检查

  • 同步后必须验证 index.html 的 div 平衡(opens == closes)
  • 如果不平衡,查找问题所在

🔴 第三步:本地测试

启动本地服务器(如果未运行):

cd your-project-directory
npx http-server -p 8080 &

测试步骤

# 1. 打开浏览器到首页
agent-browser goto http://localhost:8080

# 2. 导航到目标页面(使用 URL hash)
agent-browser goto http://localhost:8080/#page-name

# 3. 测试弹窗
agent-browser eval "openAddModal()"  # 打开弹窗
agent-browser screenshot test-modal.png  # 截图验证
agent-browser eval "closeModal()"  # 关闭弹窗

# 4. 关闭浏览器
agent-browser close

测试检查清单

  • 页面正常加载
  • 弹窗能打开
  • 弹窗内容正确
  • 弹窗能关闭
  • Tab 切换正常(如有)
  • 按钮点击有效

🔴 第四步:提交代码

cd your-project-directory
git add -A
git commit -m "fix: 描述修改内容"

📝 经验沉淀(2026-04-13)

问题1:弹窗函数无法调用(Uncaught ReferenceError)

症状openAddModal is not defined 等错误

根本原因

  1. main.js 引用被同步脚本删除
  2. 同步脚本移动了 <script src="main.js"></script> 位置

解决方案

// 确保 main.js 在 </main> 之后引入
</main>
<script src="scripts/main.js"></script>
</body>

问题2:所有 Tab 选中态不显示

症状:切换 Tab 时按钮背景色不变化,全部选中态显示错误

根本原因:Tab 切换函数使用全局选择器 .tabs .tab 会选中所有页面的 tab

解决方案:使用页面级作用域

// ❌ 错误:会选择所有页面的 tab
document.querySelectorAll('.tabs .tab')

// ✅ 正确:只选当前页面的 tab
document.querySelectorAll('#page-xxx .tabs > .tab')

问题3:直接访问 hash URL 时页面不显示

症状http://localhost:8080/#delivery_return 直接访问时页面 display:none

根本原因:没有 hashchange 监听,导航只用 click 事件

解决方案:在 main.js 添加 hash 路由监听

// Hash 路由监听 - 支持直接访问 #page
window.addEventListener('hashchange', () => {
  const hash = window.location.hash.replace('#', '');
  if (hash) {
    const navItem = document.querySelector(`[data-page="${hash}"]`);
    if (navItem) {
      navItem.click();
    }
  }
});

// 页面加载时检查 hash
if (window.location.hash) {
  setTimeout(() => {
    const hash = window.location.hash.replace('#', '');
    const navItem = document.querySelector(`[data-page="${hash}"]`);
    if (navItem) {
      navItem.click();
    }
  }, 100);
}

问题4:表格横向滚动侵入左侧菜单

症状:表格内容多时,整个页面宽度被撑开,侵入左侧菜单栏

根本原因

  1. 表格使用 width:100% + min-width:1400px 矛盾
  2. 容器没有正确设置 overflow:hidden

解决方案:使用 Ant Design 风格的固定表头表格

问题5:固定表头表格实现(Ant Design 风格)

核心原理

  1. 表头和表体分离 - 用两个独立的 <table>
  2. 表头固定 - overflow:hidden,表头不滚动
  3. 表体滚动 - overflow:auto;max-height:440px
  4. 固定列用 position:sticky - left:0 / right:0
  5. 固定表格布局 - table-layout:fixed + colgroup 定义列宽

代码模板

<!-- 外层容器 -->
<div class="ant-table-wrapper" style="display:flex;flex-direction:column;overflow:hidden;height:500px;">
  
  <!-- 表头 - 固定不滚动 -->
  <div class="ant-table-header" style="overflow:hidden;border-bottom:2px solid #E5E7EB;flex-shrink:0;">
    <table style="table-layout:fixed;width:1800px;border-collapse:collapse;font-size:14px;">
      <colgroup>
        <col style="width:48px;"><!-- 复选框 -->
        <col style="width:180px;"><!-- 单号 -->
        <col style="width:100px;"><!-- 其他列... -->
        <!-- ... -->
      </colgroup>
      <thead><tr>
        <th style="position:sticky;left:0;background:#F9FAFB;z-index:4;"><input type="checkbox"></th>
        <th style="position:sticky;left:48px;background:#F9FAFB;z-index:3;">单号</th>
        <th>字段1</th>
        <th>字段2</th>
        <!-- ... -->
        <th style="position:sticky;right:0;background:#F9FAFB;z-index:3;">操作</th>
      </tr></thead>
    </table>
  </div>
  
  <!-- 表体 - 独立滚动 -->
  <div class="ant-table-body" style="overflow:auto;max-height:440px;">
    <table style="table-layout:fixed;width:1800px;border-collapse:collapse;font-size:14px;">
      <colgroup>
        <!-- 与表头相同的列宽定义 -->
      </colgroup>
      <tbody>
        <tr>
          <td style="position:sticky;left:0;background:white;z-index:2;"><input type="checkbox"></td>
          <td style="position:sticky;left:48px;background:white;z-index:2;">单号值</td>
          <td>字段1值</td>
          <!-- ... -->
          <td style="position:sticky;right:0;background:white;z-index:2;"><button>操作</button></td>
        </tr>
      </tbody>
    </table>
  </div>
</div>

关键样式

元素样式说明
表头 wrapperoverflow:hidden;flex-shrink:0不滚动
表体 wrapperoverflow:auto;max-height:440px垂直滚动
表头表格table-layout:fixed;width:固定值固定列宽
固定列 th/tdposition:sticky;left:0;z-index:2左侧固定
右侧固定列position:sticky;right:0右侧固定
固定列背景background:#F9FAFB (表头) / background:white (表体)避免透明

问题6:筛选条件缺少查询/重置按钮

解决方案

<div class="table-filters">
  <!-- 其他筛选字段... -->
  <div class="filter-group" style="display:flex;gap:8px;">
    <button class="btn btn-outline btn-sm" onclick="resetXxxFilters()">重置</button>
    <button class="btn btn-primary btn-sm" onclick="searchXxx()">查询</button>
  </div>
</div>

对应 JS 函数

function resetXxxFilters() {
  document.querySelectorAll('#page-xxx .filter-select').forEach(s => s.selectedIndex = 0);
  document.querySelectorAll('#page-xxx .filter-input').forEach(i => i.value = '');
}

function searchXxx() {
  alert('查询功能 - 实际项目中会调用API过滤数据');
}

问题7:列表数据缺少复选框

解决方案:每行数据第一个 td 添加复选框

<tr>
  <td style="position:sticky;left:0;z-index:1;background:white;">
    <input type="checkbox">
  </td>
  <td style="position:sticky;left:48px;z-index:1;background:white;">
    <span style="font-family:monospace;font-weight:500;">FC-2026-0410-001</span>
  </td>
  <!-- 其他字段... -->
</tr>

问题8:script 标签格式导致同步失败

症状:同步后弹窗函数丢失

原因</script> 后面没有正确换行,导致被当作 HTML 标签解析

解决方案

<!-- ❌ 错误 -->
<script>function foo(){}</script><div>

<!-- ✅ 正确 -->
<script>
function foo(){}
</script>
<div>

常见问题速查表

问题症状解决方案
弹窗打不开xxx is not defined检查 main.js 引用位置和函数定义
Tab 选中态错乱所有 Tab 同时选中用页面级作用域选择器
Hash URL 不工作页面 display:none添加 hashchange 监听
表格侵入侧边栏横向滚动时布局乱用 Ant Design 固定表头表格
复选框丢失数据行没有 checkbox手动添加每个数据行的复选框
同步后功能失效弹窗/按钮不工作检查 script 标签格式和 div 平衡

📝 WMS 项目实战经验(2026-04-14)

经验1:弹窗 HTML 必须在页面 div 内部

问题现象:"新增组织"按钮点击无反应,弹窗无法打开

排查过程

  1. 检查按钮 onclick 处理函数 → 存在
  2. 检查 JavaScript 函数 → 存在
  3. 检查弹窗 HTML 位置 → 发现弹窗放在了页面 div 外部

根本原因

<!-- ❌ 错误:弹窗在页面 div 外部 -->
<div id="page-system_org" class="page">
  ...页面内容...
</div>
<!-- 弹窗在这里,页面关闭后的位置 -->
<div id="modal-add-org" style="display:none...">...</div>

<!-- ✅ 正确:弹窗必须在页面 div 内部 -->
<div id="page-system_org" class="page">
  ...页面内容...
  <!-- 弹窗必须放在这里 -->
  <div id="modal-add-org" style="display:none...">...</div>
</div>

教训:弹窗 HTML 放在 </div> (页面关闭标签) 之后导致弹窗不显示。必须在页面 div 内部。


经验2:JavaScript 语法错误会导致整个脚本块失效

问题现象:修复弹窗位置后,按钮仍然无法点击

排查过程

  1. 使用 prototype-design 技能的调试方法
  2. 检查 div 平衡 → 发现页面 div 缺少闭合标签
  3. 修复 div 后检查脚本块 → 发现多余的 }

根本原因submitNewException() 函数后有一个多余的闭合括号 },导致 JavaScript 语法错误,整个脚本块无法执行

// ❌ 错误:函数后有多余的 }
function submitNewException() {
  // ...表单提交逻辑
}
//}  <-- 多余的括号导致语法错误

// ✅ 正确:括号匹配
function submitNewException() {
  // ...表单提交逻辑
}

教训:修改 HTML 时要确保 div 平衡;修改 JS 时要确保括号匹配。使用 Python 脚本做精确替换比手动编辑更安全。


经验3:按钮必须手动绑定 onclick

问题现象:新增弹窗后,弹窗中的按钮点击无反应

根本原因:新增的弹窗按钮没有 onclick 属性

解决方案:每个按钮都要显式绑定 onclick

<!-- ❌ 错误:按钮没有 onclick -->
<button class="btn btn-primary">确定</button>

<!-- ✅ 正确:显式绑定 onclick -->
<button onclick="submitAddOrg()" class="btn btn-primary">确定</button>

教训:复制弹窗模板时,别忘了修改按钮的 onclick 属性。


经验4:按钮样式统一使用 CSS 类

问题现象:各页面按钮样式不统一,有内联样式如 style="padding:10px 24px;"

解决方案:统一使用 CSS 类

<!-- ✅ 主按钮:黑色背景,胶囊形状 -->
<button class="btn btn-primary btn-sm">新增</button>

<!-- ✅ 次按钮:白色背景,灰色边框 -->
<button class="btn btn-outline btn-sm">取消</button>

按钮样式规范

元素类名样式
主按钮.btn .btn-primary黑色背景 #111827,胶囊形
次按钮.btn .btn-outline白色背景,#E5E7EB 边框
按钮尺寸.btn-smpadding: 6px 12px; font-size: 12px
胶囊形状border-radius: 50px用于主、次按钮

经验5:Python 脚本精确替换避免手动错误

问题现象:手动编辑 HTML 时容易出现 div 不平衡、括号遗漏等问题

解决方案:使用 Python 脚本做精确文本替换

with open('index.html', 'r') as f:
    content = f.read()

# 替换前先备份
# ...

# 精确替换一段 HTML
old_text = '''<button class="btn btn-primary btn-sm">
              新增模块
            </button>'''

new_text = '''<button class="btn btn-primary btn-sm" onclick="openAddModuleModal()">
              新增模块
            </button>'''

content = content.replace(old_text, new_text)

with open('index.html', 'w') as f:
    f.write(content)

# 验证 div 平衡
print(f'opens={content.count("<div")}, closes={content.count("</div>")}, diff={content.count("<div")-content.count("</div>")}')

教训:用 Python 脚本做精确替换,比手动编辑更可靠,尤其是涉及多行 HTML 时。


经验6:系统管理模块弹窗字段设计

新增模块弹窗字段

字段类型说明
模块编码text必填,如 SYS_010
模块名称text必填
上级模块select无(顶级模块)/系统管理/基础档案...
模块分类select系统管理/基础档案/作业配置...
菜单层级select一级/二级/三级菜单
菜单图标textemoji 格式
路由路径text如 /system/module
排序number数字越小越靠前
状态select启用/禁用
备注textarea可选

新增组织弹窗字段

字段类型说明
组织编码text必填,如 ORG_010
组织名称text必填
上级组织select母公司/北京仓/上海仓...
组织类型select公司/仓库/部门/作业组
负责人text可选
联系电话tel可选
所在地区text如 北京市/朝阳区
状态select启用/禁用
组织地址textarea可选

新增用户弹窗字段

字段类型说明
用户名text必填
登录密码password必填,6位以上
姓名text必填
手机号tel必填
邮箱email可选
所属组织select必填,北京仓-收货组/上海仓...
用户角色select超级管理员/仓库管理员/作业员...
岗位text可选
入职日期date可选
状态select正常/禁用/待审核

经验7:数据统计报表页面丰富度提升

质量与异常报表增强

  • 异常趋势图(近7天柱状图)
  • 异常类型分布(货损42%/配送延误28%等)
  • 闭环率分析(96.8%闭环率、2.5h平均处理时长)

对账准确率监察增强

  • KPI 从 4 个扩展到 6 个
  • 新增已核销/核对中指标

作业报表增强

  • 新增时效达标率分析(拣货98.5%/上架97.8%等)

库存报表增强

  • 新增滞销库存预警(30天以上)
  • 新增安全库存预警

经验8:复合条件定位按钮

问题现象:页面有多个相似按钮(如"新增"),用 document.querySelector 定位困难

解决方案:使用复合条件或更精确的选择器

// ❌ 困难:页面有多个 btn-primary
document.querySelector('.btn.btn-primary')

// ✅ 更好:使用 onclick 属性定位
document.querySelector('button[onclick="openAddModuleModal()"]')

// ✅ 更好:在页面内部查找
document.querySelector('#page-system_module button[onclick="openAddModuleModal()"]')

经验9:修改后立即验证

原则:每次修改后立即验证,不要积累多个问题

验证清单

# 1. 检查 div 平衡
python3 -c "with open('index.html') as f: c=f.read(); print(f'opens={c.count(\"<div\")}, closes={c.count(\"</div>\")}, diff={c.count(\"<div\")-c.count(\"</div>\")}')"

# 2. 截图验证
agent-browser goto http://localhost:8080/#page-name
agent-browser eval "openAddModal()"
agent-browser screenshot test-modal.png
agent-browser close

# 3. 提交代码
git add -A && git commit -m "feat: 描述"

经验10:单页 HTML 项目的好处

为什么 WMS 原型用单页 HTML 没有问题

  1. Compaction 只压缩对话历史:不影响 HTML 文件本身
  2. 不是日志文件:不会无限增长
  3. 每次修改同一个文件:不是追加模式
  4. 3700+ divs 仍然高效:文件体积适中,渲染快

什么时候该拆分成多文件

  • 单个文件超过 2MB
  • 需要多人协作(git merge 冲突)
  • 模块之间完全独立

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

Cjl Slides

Create stunning HTML presentations in 24 international design styles with strict design rules. Export to .pptx for PowerPoint editing. ## Design Philosophy -...

Registry SourceRecently Updated
1150Profile unavailable
General

Post Creator

Generate beautiful single-page HTML posters with various styles. Use when users want to create posters, flyers, promotional images, marketing materials, or v...

Registry Source
2540Profile unavailable
General

Frontend Design

Build polished, conversion-aware frontends with strong visual taste, clear hierarchy, and production-grade HTML/CSS/JS. Landing pages, dashboards, components...

Registry Source
7622Profile unavailable
General

Infographic Image

将任意主题、长文、报告、纪要或说明文本稳定转换成中文视觉生图提示词, 然后调用 DashScope Qwen 图像模型直接出图。适用于“把这段内容做成信息图”、 “做成故事漫画长图”、“长文转图”、“生成中文生图 prompt”、“根据文档出图” 等场景;默认采用白色磁吸会议板手写风格,也支持切换到其他内置模板。

Registry SourceRecently Updated
3760Profile unavailable