通用页面原型设计技能。基于 58种设计风格(默认Figma 风格) UI 的复杂业务系统 HTML 原型开发, 包括标准页面结构、统计卡片、筛选条件、数据表格、弹窗设计。 适用场景:(1) 创建新的管理页面原型 (2) 对照业务文档实现功能模块 (3) 设计弹窗和详情页 (4) 构建带看板和列表视图的页面。 **样式选择**:内置58+设计系统,直接引用 `references/design-systems/` 目录。 使用时指定 DESIGN_SYSTEM=<名称> 即可应用对应设计风格。
Scanned 9/9/2026
Install to Claude Code
npx -y skills add Lord1Egypt/awesome-skill-forge --skill prototype-design --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Prototype Design?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/lord1egypt-prototype-design)More formats (shields.io, HTML) on the badges page.
---
name: prototype-design
description: |
通用页面原型设计技能。基于 58种设计风格(默认Figma 风格) UI 的复杂业务系统 HTML 原型开发,
包括标准页面结构、统计卡片、筛选条件、数据表格、弹窗设计。
适用场景:(1) 创建新的管理页面原型 (2) 对照业务文档实现功能模块
(3) 设计弹窗和详情页 (4) 构建带看板和列表视图的页面。
**样式选择**:内置58+设计系统,直接引用 `references/design-systems/` 目录。
使用时指定 DESIGN_SYSTEM=<名称> 即可应用对应设计风格。
origin: openclaw
last_updated: 2026-04-14
changelog:
- 2026-04-14: 初始版本,从 WMS 项目经验中沉淀
- 2026-04-14: 新增长对话上下文管理章节
- 2026-04-14: 新增 WMS 项目实战经验(10条核心教训)
---
# 原型设计技能
## When to Use(何时使用)
- 用户要求创建新的管理页面原型
- 需要实现业务文档中的功能模块
- 需要设计弹窗、详情页
- 需要构建带看板或列表视图的页面
- 涉及 HTML/CSS/JS 前端代码的原型开发
## How to Use(如何使用)
### 1. 项目初始化
```bash
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**
```bash
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` | 紫色渐变、高级感 |
## 标准页面结构
```html
<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>
```
## 弹窗设计规范
### 标准弹窗模板
```html
<!-- 遮罩 + 居中弹窗 -->
<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>
```
### 弹窗样式要点(必记)
| 元素 | 样式属性 | 正确值 | 常见错误 |
|------|---------|--------|---------|
| **外层遮罩** | `position` | `fixed` | 用 `absolute` 会滚动 |
| **遮罩背景** | `background` | `rgba(0,0,0,0.6)` | `0.5` 太淡,`0.7` 太浓 |
| **遮罩定位** | `top/left/right/bottom` | `0`(全屏覆盖) | 忘记设置任一边 |
| **弹窗容器** | `display` | `flex` | 父级用 flex 居中 |
| **居中方式** | `align-items + justify-content` | `center` + `center` | 缺少任一属性 |
| **内容框圆角** | `border-radius` | `16px` | `12px` 不够现代 |
| **内容框宽度** | `width` | `560px` 或 `90vw` | 固定 px 在小屏幕不友好 |
| **内容框高度** | `max-height` | `85vh` | `80vh` 可能显示不全 |
| **阴影** | `box-shadow` | `0 25px 80px rgba(0,0,0,0.35)` | 太淡看不出层次 |
### 表单项样式要点
| 元素 | 样式属性 | 正确值 |
|------|---------|--------|
| **输入框/下拉框** | `padding` | `10px 12px` |
| **输入框/下拉框** | `border-radius` | `8px` |
| **输入框/下拉框** | `border` | `1px solid #E5E7EB` |
| **输入框/下拉框** | `font-size` | `14px` |
| **textarea** | `resize` | `none` |
| **textarea** | `box-sizing` | `border-box` |
| **必填标记** | color | `#EF4444` (红色) |
### ⚠️ 常见错误
1. **不要使用 CSS 类名**:如 `class="modal"`、`class="btn btn-primary"` - 这些类通常没有定义样式或样式被覆盖
2. **必须使用内联样式**:弹窗组件应完全使用内联样式,避免外部 CSS 干扰
3. **遮罩层必须有 `z-index:1000`**:确保在最上层
4. **内容框不能用 `overflow:hidden`**:内容超出需要滚动,必须用 `overflow-y:auto`
## 看板视图
```html
<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 切换
```html
<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>` (页面关闭标签) **之后**
```html
<!-- ❌ 错误:弹窗在页面 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` 会影响所有页面
```javascript
// ❌ 错误:会选择页面上所有的 tab
const tabs = document.querySelectorAll('.tabs .tab');
// ✅ 正确:使用页面级作用域
const tabs = document.querySelectorAll('#page-xxx .tabs > .tab');
```
**Tab 切换函数模板**:
```javascript
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/` 目录的页面后,必须运行以下同步脚本:
```bash
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
```bash
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. 分阶段提交策略(小步提交)
**原则**:每个功能完成后立即提交,避免大量变更堆积
```bash
# ✅ 好的做法:功能完成即提交
git add -A && git commit -m "feat: 新增用户管理弹窗"
# ❌ 不好的做法:多个功能一起提交
git add -A && git commit -m "feat: 多项优化"
```
**好处**:
- 如果出问题,容易回溯
- 减少每次提交的文件变更量
- Compaction 后的 summary 更简洁
#### 4. 保持文件平衡
每次 HTML 修改后检查 div 平衡:
```bash
# 检查 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`:
```markdown
## 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](references/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
**必须运行同步脚本**:
```bash
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)
- 如果不平衡,查找问题所在
### 🔴 第三步:本地测试
**启动本地服务器**(如果未运行):
```bash
cd your-project-directory
npx http-server -p 8080 &
```
**测试步骤**:
```bash
# 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 切换正常(如有)
- [ ] 按钮点击有效
### 🔴 第四步:提交代码
```bash
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>` 位置
**解决方案**:
```javascript
// 确保 main.js 在 </main> 之后引入
</main>
<script src="scripts/main.js"></script>
</body>
```
### 问题2:所有 Tab 选中态不显示
**症状**:切换 Tab 时按钮背景色不变化,全部选中态显示错误
**根本原因**:Tab 切换函数使用全局选择器 `.tabs .tab` 会选中**所有页面**的 tab
**解决方案**:使用页面级作用域
```javascript
// ❌ 错误:会选择所有页面的 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 路由监听
```javascript
// 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 定义列宽
**代码模板**:
```html
<!-- 外层容器 -->
<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>
```
**关键样式**:
| 元素 | 样式 | 说明 |
|------|------|------|
| 表头 wrapper | `overflow:hidden;flex-shrink:0` | 不滚动 |
| 表体 wrapper | `overflow:auto;max-height:440px` | 垂直滚动 |
| 表头表格 | `table-layout:fixed;width:固定值` | 固定列宽 |
| 固定列 th/td | `position:sticky;left:0;z-index:2` | 左侧固定 |
| 右侧固定列 | `position:sticky;right:0` | 右侧固定 |
| 固定列背景 | `background:#F9FAFB` (表头) / `background:white` (表体) | 避免透明 |
### 问题6:筛选条件缺少查询/重置按钮
**解决方案**:
```html
<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 函数**:
```javascript
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 添加复选框
```html
<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 标签解析
**解决方案**:
```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 外部**
**根本原因**:
```html
<!-- ❌ 错误:弹窗在页面 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 语法错误,整个脚本块无法执行
```javascript
// ❌ 错误:函数后有多余的 }
function submitNewException() {
// ...表单提交逻辑
}
//} <-- 多余的括号导致语法错误
// ✅ 正确:括号匹配
function submitNewException() {
// ...表单提交逻辑
}
```
**教训**:修改 HTML 时要确保 div 平衡;修改 JS 时要确保括号匹配。使用 Python 脚本做精确替换比手动编辑更安全。
---
### 经验3:按钮必须手动绑定 onclick
**问题现象**:新增弹窗后,弹窗中的按钮点击无反应
**根本原因**:新增的弹窗按钮**没有 onclick 属性**
**解决方案**:每个按钮都要显式绑定 onclick
```html
<!-- ❌ 错误:按钮没有 onclick -->
<button class="btn btn-primary">确定</button>
<!-- ✅ 正确:显式绑定 onclick -->
<button onclick="submitAddOrg()" class="btn btn-primary">确定</button>
```
**教训**:复制弹窗模板时,别忘了修改按钮的 onclick 属性。
---
### 经验4:按钮样式统一使用 CSS 类
**问题现象**:各页面按钮样式不统一,有内联样式如 `style="padding:10px 24px;"`
**解决方案**:统一使用 CSS 类
```html
<!-- ✅ 主按钮:黑色背景,胶囊形状 -->
<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-sm` | `padding: 6px 12px; font-size: 12px` |
| 胶囊形状 | `border-radius: 50px` | 用于主、次按钮 |
---
### 经验5:Python 脚本精确替换避免手动错误
**问题现象**:手动编辑 HTML 时容易出现 div 不平衡、括号遗漏等问题
**解决方案**:使用 Python 脚本做精确文本替换
```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 | 一级/二级/三级菜单 |
| 菜单图标 | text | emoji 格式 |
| 路由路径 | 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` 定位困难
**解决方案**:使用复合条件或更精确的选择器
```javascript
// ❌ 困难:页面有多个 btn-primary
document.querySelector('.btn.btn-primary')
// ✅ 更好:使用 onclick 属性定位
document.querySelector('button[onclick="openAddModuleModal()"]')
// ✅ 更好:在页面内部查找
document.querySelector('#page-system_module button[onclick="openAddModuleModal()"]')
```
---
### 经验9:修改后立即验证
**原则**:每次修改后立即验证,不要积累多个问题
**验证清单**:
```bash
# 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 冲突)
- 模块之间完全独立
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!