| name | html-widget |
| description | 生成可交互的 HTML 可视化组件 — 算法动画、数据图表、概念演示、交互式教程等。当用户需要交互式演示、动画解释、数据探索、UI 原型等场景时触发,直接输出 html 代码块,前端会在安全沙箱中渲染为可交互组件。 |
| trigger | 用户要求做图表、可视化、交互式演示、动画演示、数据展示、仪表盘、计数器、计时器、小工具、小游戏、算法可视化、UI 原型/mockup、交互式教程,或任何适合用交互/动画来呈现的内容 |
| do-not-trigger | 能用 mermaid 解决的结构化图表(流程图、ER图、时序图);能用 infographic 解决的数据海报/信息图;用户明确要求生成代码文件而非演示;纯文字能说清的简单问题 |
| user-invocable | true |
| disable-auto-invoke | false |
| argument-hint | <交互组件描述> |
| tags | ["交互","interactive","可视化","visualization","动画","animation","widget","演示","demo","图表","chart"] |
你现在帮用户生成 可交互的 HTML 组件。直接在回复中输出 ```html 代码块,前端会在安全沙箱 iframe 中自动渲染为可交互组件。
重要规则:
- 不要调用 generate_image 工具,HTML 代码块就是最终输出
- 不要调用文件写入工具(write_file 等)把 HTML 保存到本地——这是对话内的临时可视化,不是文件交付物
- 不要用 Artifacts 模式——直接输出代码块即可
- 不要调用 report_plan / todo_write——直接输出代码块,不需要规划步骤
何时用可视化 vs 写文件
| 用户意图 | 做法 |
|---|
| "看看"、"分析一下"、"展示"、"解释"、"演示" | → 输出 ```html 代码块(可视化) |
| "导出"、"保存"、"生成报表"、"做个文件"、"发给领导" | → 调用 write_file(文件交付) |
| "分析数据,然后做个报表" | → 先可视化展示关键发现,再写文件 |
输出格式
输出 ```html 代码块,内容为 HTML 片段(不含 <!DOCTYPE>、<html>、<head>、<body> 标签,这些由渲染器自动包裹)。
必须按此顺序:<style> → HTML 内容 → <script>
<style>
</style>
<div id="app">
</div>
<script>
</script>
尺寸规范
根据内容复杂度选择合适的尺寸:
| 类型 | 高度 | 适用场景 |
|---|
| 紧凑型 | ≤150px | 单个指标卡片、迷你图、简单计数器 |
| 标准型 | 150-400px | 单个图表、数据表格、简单交互 |
| 完整型 | 400-700px | 仪表盘、多图表组合、复杂交互 |
- 宽度:始终自适应 100%
- body 已预设 16px 内边距
设计规范
核心审美原则:克制、留白、精致。宁可简洁到"少了点什么",也不要堆砌到"多了点什么"。每个像素都要有存在的理由。
配色体系
必须使用浅色/白色背景。禁止深色/黑色背景。
基础色(CSS 变量,已预注入):
| 变量 | 用途 | 值 |
|---|
--abu-primary | 主色/强调色 | #d97757(暖橙) |
--abu-text | 正文文字 | #29261b(深棕) |
--abu-text-muted | 次要文字/辅助信息 | #888579(暖灰) |
--abu-bg | 页面背景 | #ffffff |
--abu-bg-secondary | 卡片/区块背景 | #f5f3ee(米白) |
--abu-border | 边框/分割线 | #e5e2db(浅驼) |
数据色板(图表数据系列,按优先级排列):
| 序号 | 色值 | 名称 | 适用 |
|---|
| 1 | #4F46E5 | 靛蓝 | 默认首选、主数据系列 |
| 2 | #0891B2 | 青色 | 对比系列 |
| 3 | #10B981 | 翠绿 | 正向指标(增长/完成) |
| 4 | #F59E0B | 琥珀 | 警告/中性指标 |
| 5 | #EF4444 | 赤红 | 负向指标(下降/错误) |
| 6 | #7C3AED | 紫色 | 补充系列 |
| 7 | #EC4899 | 玫红 | 补充系列 |
语义色:
- 成功/增长:
#10B981,搭配浅底 #ecfdf5
- 危险/下降:
#EF4444,搭配浅底 #fef2f2
- 警告:
#F59E0B,搭配浅底 #fffbeb
- 信息:
#4F46E5,搭配浅底 #eef2ff
配色原则:
- 白色基底 + 单一强调色。一个组件最多 1 种强调色 + 数据色板
- 大面积只用白/米白/浅灰,色彩只用于数据点、状态标识、CTA 按钮
- 图表背景透明,不要给 chart container 加背景色
- 相邻数据系列色相间距 > 60°,确保色盲友好
间距系统(4px 网格)
所有间距使用 4 的倍数:
| token | 值 | 用途 |
|---|
--s-1 | 4px | 图标与文字间距、紧凑元素内边距 |
--s-2 | 8px | 相关元素间距、小卡片内边距 |
--s-3 | 12px | 组内元素间距、按钮组 gap |
--s-4 | 16px | 段落间距、卡片内边距(默认) |
--s-5 | 20px | 区块间距 |
--s-6 | 24px | 大卡片内边距、区块间隔 |
--s-8 | 32px | 主要分区间隔 |
用法示例:padding: 16px、gap: 12px、margin-bottom: 24px。不要出现 5px、7px、15px 这种非 4 倍数值。
圆角
| 场景 | 值 |
|---|
| 小元素(标签、徽章) | 4px |
| 按钮、输入框 | 6px |
| 卡片、面板 | 10px |
| 大容器、模态框 | 12px |
统一使用 border-radius,不要混用不同的圆角值。一个组件内圆角值最多 2 种。
字体与排版
- 字体栈:
system-ui, -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif
- 字重:仅
400(正文)和 600(标题/强调),禁用 300/500/700/800
- 等宽数字:数值型内容加
font-variant-numeric: tabular-nums 确保对齐
| 层级 | 字号 | 字重 | 行高 | 用途 |
|---|
| 大标题 | 24px | 600 | 1.3 | 仪表盘主标题 |
| 标题 | 18px | 600 | 1.4 | 区块标题、图表标题 |
| 小标题 | 15px | 600 | 1.5 | 卡片标题 |
| 正文 | 14px | 400 | 1.6 | 默认正文 |
| 辅助 | 12px | 400 | 1.5 | 标签、图例、脚注、数据来源 |
| 超大数字 | 28-36px | 600 | 1.2 | KPI 核心数值 |
中文排版规则:
- 中英文/中文与数字之间加半角空格:
GDP 总量 101.4 万亿
- 数字千分位:
¥1,234,567
- 日期格式:
2024 年 3 月 或 2024-03-15
- 百分比:正数加
+ 号 → +12.3%
- 标点用中文全角:
, 。 、 ( )
组件模式
卡片(Card)
.card {
background: var(--abu-bg-secondary);
border-radius: 10px;
padding: 16px;
}
.card-title {
font-size: 12px;
font-weight: 400;
color: var(--abu-text-muted);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 8px;
}
- 卡片之间 gap
12px
- 卡片内不要再嵌套卡片(最多一层)
- 卡片标题用辅助字号(12px)+ muted 色,不要用大标题
KPI 指标卡
.kpi-value {
font-size: 28px;
font-weight: 600;
color: var(--abu-text);
font-variant-numeric: tabular-nums;
line-height: 1.2;
}
.kpi-trend {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 12px;
font-weight: 600;
margin-top: 4px;
padding: 2px 6px;
border-radius: 4px;
}
.kpi-trend.up { color: #10B981; background: #ecfdf5; }
.kpi-trend.down { color: #EF4444; background: #fef2f2; }
- 趋势指示用
↑ ↓ 字符 + 语义色背景标签,而不是纯文字
- 数值居左对齐,不要居中
数据表格(Table)
.data-table {
width: 100%;
border-collapse: separate;
border-spacing: 0;
font-size: 13px;
}
.data-table th {
text-align: left;
font-weight: 600;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--abu-text-muted);
padding: 8px 12px;
border-bottom: 2px solid var(--abu-border);
}
.data-table td {
padding: 10px 12px;
border-bottom: 1px solid var(--abu-border);
color: var(--abu-text);
}
.data-table tr:last-child td { border-bottom: none; }
.data-table tr:hover td { background: var(--abu-bg-secondary); }
- 表头用小字 + 大写 + muted 色,营造层次感
- 数字列右对齐 +
tabular-nums
- 不要给表格加外边框,只用行分割线
标签页(Tabs)
.tabs {
display: flex;
gap: 0;
border-bottom: 1px solid var(--abu-border);
margin-bottom: 16px;
}
.tab {
padding: 8px 16px;
font-size: 13px;
color: var(--abu-text-muted);
border: none;
background: none;
cursor: pointer;
border-bottom: 2px solid transparent;
margin-bottom: -1px;
transition: color 0.15s, border-color 0.15s;
}
.tab:hover { color: var(--abu-text); }
.tab.active {
color: var(--abu-primary);
border-bottom-color: var(--abu-primary);
font-weight: 600;
}
徽章/标签(Badge)
.badge {
display: inline-flex;
align-items: center;
padding: 2px 8px;
font-size: 11px;
font-weight: 600;
border-radius: 4px;
letter-spacing: 0.3px;
}
.badge-success { color: #10B981; background: #ecfdf5; }
.badge-danger { color: #EF4444; background: #fef2f2; }
.badge-warning { color: #D97706; background: #fffbeb; }
.badge-info { color: #4F46E5; background: #eef2ff; }
进度条(Progress Bar)
.progress-track {
height: 6px;
background: var(--abu-border);
border-radius: 3px;
overflow: hidden;
}
.progress-fill {
height: 100%;
border-radius: 3px;
background: var(--abu-primary);
transition: width 0.4s ease;
}
分割线
.divider {
height: 1px;
background: var(--abu-border);
margin: 16px 0;
}
按钮
基础按钮样式已预设在渲染器中,直接用 <button> 即可。
.btn-primary {
background: var(--abu-primary);
color: #fff;
border-color: var(--abu-primary);
}
.btn-primary:hover { opacity: 0.9; }
.btn-secondary {
background: var(--abu-bg-secondary);
border-color: var(--abu-border);
}
.btn-sm { padding: 4px 8px; font-size: 12px; }
.btn-group { display: inline-flex; gap: 8px; }
- 按钮文字简洁:2-4 个字(
开始 重置 下一步)
- 一组操作最多 1 个主要按钮,其余用默认样式
- 禁用态:
opacity: 0.5; pointer-events: none
布局模式
网格仪表盘
.grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; }
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
.grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
- KPI 行:3-4 列
- 图表区:1 列(全宽)或 2 列
- 不要超过 4 列
Flex 布局
.flex-between {
display: flex;
align-items: center;
justify-content: space-between;
}
.flex-center {
display: flex;
align-items: center;
justify-content: center;
}
标准页面结构
┌─ 标题区(标题 + 副标题/数据来源)──────────────────┐
│ │
├─ KPI 行(3-4 个指标卡片)──────────────────────────┤
│ │
├─ 主图表区(全宽或左右分栏)────────────────────────┤
│ │
├─ 辅助区(表格/列表/补充图表)──────────────────────┤
│ │
└─ 脚注(数据来源、时间戳)──────────────────────────┘
图表规范
Chart.js 标准配置
Chart.defaults.font.family = "system-ui, -apple-system, 'PingFang SC', sans-serif";
Chart.defaults.font.size = 12;
Chart.defaults.color = '#888579';
Chart.defaults.plugins.legend.labels.usePointStyle = true;
Chart.defaults.plugins.legend.labels.boxWidth = 8;
Chart.defaults.plugins.legend.labels.boxHeight = 8;
Chart.defaults.plugins.legend.labels.padding = 16;
const chartOptions = {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom',
labels: { usePointStyle: true, boxWidth: 8, boxHeight: 8, padding: 16 }
},
tooltip: {
backgroundColor: '#29261b',
titleFont: { size: 13 },
bodyFont: { size: 12 },
padding: 10,
cornerRadius: 6,
displayColors: true,
}
},
scales: {
x: {
grid: { display: false },
ticks: { font: { size: 12 } },
border: { color: '#e5e2db' }
},
y: {
grid: { color: '#f5f3ee' },
ticks: { font: { size: 12 } },
border: { display: false }
}
}
};
ECharts 标准配置
const echartsTheme = {
backgroundColor: 'transparent',
textStyle: { fontFamily: "system-ui, -apple-system, 'PingFang SC', sans-serif" },
title: { textStyle: { fontSize: 16, fontWeight: 600, color: '#29261b' } },
legend: { bottom: 0, itemWidth: 12, itemHeight: 8, textStyle: { fontSize: 12, color: '#888579' } },
grid: { left: 60, right: 20, top: 40, bottom: 50, containLabel: false },
xAxis: { axisLine: { lineStyle: { color: '#e5e2db' } }, splitLine: { show: false } },
yAxis: { axisLine: { show: false }, splitLine: { lineStyle: { color: '#f5f3ee' } } },
tooltip: {
backgroundColor: '#29261b', borderWidth: 0, textStyle: { color: '#fff', fontSize: 12 },
padding: [8, 12], extraCssText: 'border-radius: 6px;'
},
color: ['#4F46E5', '#0891B2', '#10B981', '#F59E0B', '#EF4444', '#7C3AED', '#EC4899']
};
图表设计原则
- 标题在图表外部用 HTML 写,不用 Chart.js/ECharts 内置标题
- 图例放底部,用圆点样式,不要用方块
- 网格线极淡(
#f5f3ee)或隐藏,不要用深色
- 柱状图:圆角
borderRadius: 6,柱宽适中不要太细
- 折线图:线宽
2px,圆点 4px,hover 6px
- 饼图/环图:环图优先(更现代),中间显示总计
- 数值格式化:Y 轴大数字用
万 亿 单位,tooltip 显示完整数字
- 脚注生成时间:不要硬编码日期,用
<span class="generated-date"></span> 占位,JS 中用 new Date().toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit' }).replace(/\//g, '-') 动态填充
动画与交互
- 过渡:仅
background、color、border-color、opacity、transform,时长 0.15s
- hover 效果:卡片
background 微调亮度,按钮 opacity: 0.9 或 background 变色
- 可点击元素:必须有
cursor: pointer + hover 视觉变化
- 禁止:
box-shadow 变化、filter: blur()、大幅 transform 动画(流式渲染不兼容)
- 图表动画:使用库内置动画即可(Chart.js 默认 1s easing),不要自定义复杂动画
禁止清单
- ❌ 深色/黑色背景
- ❌ 渐变(
linear-gradient / radial-gradient)
- ❌ 阴影(
box-shadow)、模糊(filter: blur)
- ❌
position: fixed(iframe 内无效)
- ❌ localStorage / sessionStorage(sandbox 限制)
- ❌ fetch / XHR 网络请求(所有数据内联)
- ❌ ES module
import(用 <script src=""> 引入)
- ❌ 非 4 倍数间距(5px, 7px, 15px 等)
- ❌ 超过 2 种字重(只能 400 和 600)
- ❌ 纯装饰性元素(无意义的 icon、分隔符、装饰线条)
- ❌ 手写 SVG icon(用 Unicode 字符替代:
↑ ↓ ● ▶ ◀ ✓ ✕ →)
可用外部库(CDN)
<script src="https://cdn.jsdelivr.net/npm/chart.js@4"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
<script src="https://cdn.jsdelivr.net/npm/animejs@3"></script>
可用 CDN 前缀:cdn.jsdelivr.net / cdnjs.cloudflare.com / unpkg.com / esm.sh
库选择指南:
- 简单柱/线/饼图 → Chart.js(轻量,配置简单)
- 复杂交互图表、中文场景 → ECharts(功能全,中文友好)
- 自定义可视化、力导向图 → D3(灵活但代码量大)
- 动画演示 → anime.js
注意:外部脚本和内联脚本按顺序执行(渲染器已处理加载顺序)。不要用 window.onload 包裹代码,直接写即可。
场景模板
指标卡片(紧凑型)
<style>
.kpi-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
.kpi-card { background: var(--abu-bg-secondary); border-radius: 10px; padding: 16px; }
.kpi-label { font-size: 11px; font-weight: 400; color: var(--abu-text-muted); text-transform: uppercase; letter-spacing: 0.5px; }
.kpi-value { font-size: 28px; font-weight: 600; color: var(--abu-text); font-variant-numeric: tabular-nums; line-height: 1.2; margin-top: 8px; }
.kpi-trend { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; font-weight: 600; margin-top: 8px; padding: 2px 8px; border-radius: 4px; }
.kpi-trend.up { color: #10B981; background: #ecfdf5; }
.kpi-trend.down { color: #EF4444; background: #fef2f2; }
</style>
<div class="kpi-row">
<div class="kpi-card">
<div class="kpi-label">总销售额</div>
<div class="kpi-value">¥284.7 万</div>
<div class="kpi-trend up">↑ 12.3% 环比</div>
</div>
<div class="kpi-card">
<div class="kpi-label">订单量</div>
<div class="kpi-value">3,842</div>
<div class="kpi-trend down">↓ 3.1% 环比</div>
</div>
<div class="kpi-card">
<div class="kpi-label">客单价</div>
<div class="kpi-value">¥741</div>
<div class="kpi-trend up">↑ 5.8% 环比</div>
</div>
</div>
数据图表(标准型)
<style>
.chart-header { margin-bottom: 16px; }
.chart-title { font-size: 18px; font-weight: 600; color: var(--abu-text); }
.chart-subtitle { font-size: 12px; color: var(--abu-text-muted); margin-top: 4px; }
.chart-wrap { position: relative; height: 320px; }
</style>
<div class="chart-header">
<div class="chart-title">2020–2025 年 GDP 增长趋势</div>
<div class="chart-subtitle">数据来源:国家统计局 · 单位:万亿元</div>
</div>
<div class="chart-wrap">
<canvas id="chart"></canvas>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4"></script>
<script>
(function() {
Chart.defaults.font.family = "system-ui, -apple-system, 'PingFang SC', sans-serif";
Chart.defaults.color = '#888579';
new Chart(document.getElementById('chart'), {
type: 'bar',
data: {
labels: ['2020', '2021', '2022', '2023', '2024', '2025'],
datasets: [{
label: 'GDP(万亿元)',
data: [101.4, 114.9, 121.0, 126.1, 134.9, 140.2],
backgroundColor: '#4F46E5',
borderRadius: 6,
maxBarThickness: 48,
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
tooltip: {
backgroundColor: '#29261b', cornerRadius: 6, padding: 10,
titleFont: { size: 13 }, bodyFont: { size: 12 }
}
},
scales: {
x: { grid: { display: false }, border: { color: '#e5e2db' } },
y: { grid: { color: '#f5f3ee' }, border: { display: false }, beginAtZero: false }
}
}
});
})();
</script>
交互式演示(标准型)
<style>
.demo-header { margin-bottom: 16px; }
.demo-title { font-size: 18px; font-weight: 600; color: var(--abu-text); }
.demo-desc { font-size: 12px; color: var(--abu-text-muted); margin-top: 4px; }
.stage { position: relative; height: 240px; border: 1px solid var(--abu-border); border-radius: 10px; overflow: hidden; background: var(--abu-bg-secondary); }
.controls { display: flex; align-items: center; justify-content: center; gap: 8px; margin-top: 12px; }
.info { font-size: 13px; color: var(--abu-text-muted); text-align: center; margin-top: 8px; }
</style>
<div class="demo-header">
<div class="demo-title">冒泡排序可视化</div>
<div class="demo-desc">点击"下一步"逐步观察排序过程</div>
</div>
<div class="stage" id="stage"></div>
<div class="controls">
<button onclick="prev()">◀ 上一步</button>
<button class="btn-primary" onclick="next()">下一步 ▶</button>
<button onclick="autoPlay()">自动播放</button>
</div>
<div class="info" id="info">已就绪,共 8 个元素</div>
<script>
</script>
数据表格 + 图表组合(完整型)
<style>
.page-header { margin-bottom: 20px; }
.page-title { font-size: 24px; font-weight: 600; color: var(--abu-text); }
.page-subtitle { font-size: 12px; color: var(--abu-text-muted); margin-top: 4px; }
.kpi-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 20px; }
.kpi-card { background: var(--abu-bg-secondary); border-radius: 10px; padding: 16px; }
.kpi-label { font-size: 11px; color: var(--abu-text-muted); text-transform: uppercase; letter-spacing: 0.5px; }
.kpi-value { font-size: 28px; font-weight: 600; color: var(--abu-text); font-variant-numeric: tabular-nums; line-height: 1.2; margin-top: 8px; }
.kpi-trend { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; font-weight: 600; margin-top: 8px; padding: 2px 8px; border-radius: 4px; }
.kpi-trend.up { color: #10B981; background: #ecfdf5; }
.kpi-trend.down { color: #EF4444; background: #fef2f2; }
.section { margin-bottom: 20px; }
.section-title { font-size: 15px; font-weight: 600; color: var(--abu-text); margin-bottom: 12px; }
.chart-wrap { position: relative; height: 300px; background: var(--abu-bg-secondary); border-radius: 10px; padding: 16px; }
.data-table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 13px; }
.data-table th { text-align: left; font-weight: 600; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--abu-text-muted); padding: 8px 12px; border-bottom: 2px solid var(--abu-border); }
.data-table td { padding: 10px 12px; border-bottom: 1px solid var(--abu-border); }
.data-table tr:last-child td { border-bottom: none; }
.data-table .num { text-align: right; font-variant-numeric: tabular-nums; }
.footnote { font-size: 11px; color: var(--abu-text-muted); margin-top: 16px; }
</style>
<div class="page-header">
<div class="page-title">Q4 销售分析报告</div>
<div class="page-subtitle">2024 年 10–12 月 · 数据截至 2024-12-31</div>
</div>
<div class="kpi-row">
<div class="kpi-card">
<div class="kpi-label">总营收</div>
<div class="kpi-value">¥847 万</div>
<div class="kpi-trend up">↑ 15.2%</div>
</div>
<div class="kpi-card">
<div class="kpi-label">订单数</div>
<div class="kpi-value">12,458</div>
<div class="kpi-trend up">↑ 8.7%</div>
</div>
<div class="kpi-card">
<div class="kpi-label">客单价</div>
<div class="kpi-value">¥679</div>
<div class="kpi-trend down">↓ 2.1%</div>
</div>
<div class="kpi-card">
<div class="kpi-label">退货率</div>
<div class="kpi-value">3.2%</div>
<div class="kpi-trend up">↑ 0.4pp</div>
</div>
</div>
<div class="section">
<div class="section-title">月度趋势</div>
<div class="chart-wrap"><canvas id="chart"></canvas></div>
</div>
<div class="section">
<div class="section-title">分品类明细</div>
<table class="data-table">
<thead><tr><th>品类</th><th class="num">营收</th><th class="num">占比</th><th class="num">同比</th></tr></thead>
<tbody>
<tr><td>电子产品</td><td class="num">¥3,240,000</td><td class="num">38.2%</td><td class="num" style="color:#10B981">+22.1%</td></tr>
<tr><td>家居用品</td><td class="num">¥2,180,000</td><td class="num">25.7%</td><td class="num" style="color:#10B981">+11.5%</td></tr>
<tr><td>服装配饰</td><td class="num">¥1,850,000</td><td class="num">21.8%</td><td class="num" style="color:#EF4444">-3.2%</td></tr>
<tr><td>食品饮料</td><td class="num">¥1,200,000</td><td class="num">14.2%</td><td class="num" style="color:#10B981">+8.9%</td></tr>
</tbody>
</table>
</div>
<div class="footnote">数据来源:内部销售系统 · 生成时间:<span class="generated-date"></span></div>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4"></script>
<script>
(function() {
Chart.defaults.font.family = "system-ui, -apple-system, 'PingFang SC', sans-serif";
var dateEl = document.querySelector('.generated-date');
if (dateEl) dateEl.textContent = new Date().toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit' }).replace(/\//g, '-');
Chart.defaults.color = '#888579';
new Chart(document.getElementById('chart'), {
type: 'line',
data: {
labels: ['10 月', '11 月', '12 月'],
datasets: [
{ label: '营收(万元)', data: [265, 278, 304], borderColor: '#4F46E5', backgroundColor: 'rgba(79,70,229,0.08)', fill: true, tension: 0.3, pointRadius: 4, pointHoverRadius: 6, borderWidth: 2 },
{ label: '订单数(百)', data: [38, 41, 45], borderColor: '#0891B2', backgroundColor: 'transparent', tension: 0.3, pointRadius: 4, pointHoverRadius: 6, borderWidth: 2 }
]
},
options: {
responsive: true, maintainAspectRatio: false,
plugins: {
legend: { position: 'bottom', labels: { usePointStyle: true, boxWidth: 8, boxHeight: 8, padding: 16 } },
tooltip: { backgroundColor: '#29261b', cornerRadius: 6, padding: 10 }
},
scales: {
x: { grid: { display: false }, border: { color: '#e5e2db' } },
y: { grid: { color: '#f5f3ee' }, border: { display: false } }
}
}
});
})();
</script>
UI 原型 / Mockup 规范
当用户要求"画个原型"、"做个 mockup"、"设计一个页面"时,生成高保真静态原型,模拟真实 App/Web 界面。
原型设计原则
- 看起来像真的 App,不是线框图——用真实文字、真实数据、真实图标
- 固定宽度模拟设备:手机
375px、平板 768px、桌面 1024px,居中显示
- 所有文字用中文,数据用逼真的假数据
- 交互仅限视觉反馈:hover 态、active 态、tab 切换,不做真实业务逻辑
- 多页面支持:用 JS 切换
display 实现页面导航,不要用 iframe 或多个 HTML 文件
原型色彩
原型不一定用 Abu 品牌色,根据产品类型选择合适的主色:
| 产品类型 | 推荐主色 | 辅色 |
|---|
| 企业/SaaS | #4F46E5(靛蓝) | #6366F1 |
| 电商 | #EF4444(红)或 #F97316(橙) | #FCA5A5 |
| 社交/内容 | #0EA5E9(天蓝) | #38BDF8 |
| 金融 | #0891B2(青)或 #1D4ED8(深蓝) | #67E8F9 |
| 健康/生活 | #10B981(绿) | #6EE7B7 |
| 工具/效率 | #6366F1(紫)或 #8B5CF6 | #A78BFA |
| 通用/默认 | #4F46E5 | #818CF8 |
原型组件模式
手机顶部导航栏(Status Bar + Nav)
.phone-frame {
width: 375px;
margin: 0 auto;
background: #fff;
border-radius: 20px;
overflow: hidden;
border: 1px solid var(--abu-border);
}
.status-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 20px;
font-size: 12px;
font-weight: 600;
color: #1a1a1a;
}
.nav-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 16px;
font-size: 17px;
font-weight: 600;
}
底部标签栏(Tab Bar)
.tab-bar {
display: flex;
border-top: 1px solid #f0f0f0;
background: #fff;
padding: 6px 0 20px;
}
.tab-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
font-size: 10px;
color: #999;
cursor: pointer;
}
.tab-item.active { color: var(--app-primary); }
.tab-icon { font-size: 20px; }
列表项(List Cell)
.list-cell {
display: flex;
align-items: center;
padding: 12px 16px;
gap: 12px;
border-bottom: 1px solid #f5f5f5;
cursor: pointer;
}
.list-cell:hover { background: #fafafa; }
.avatar {
width: 44px;
height: 44px;
border-radius: 10px;
background: linear-gradient(135deg, #667eea, #764ba2);
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 16px;
font-weight: 600;
flex-shrink: 0;
}
卡片/内容块
.content-card {
margin: 0 16px 12px;
background: #f8f8f8;
border-radius: 12px;
padding: 16px;
}
输入框 / 搜索框
.search-bar {
margin: 8px 16px;
padding: 10px 12px;
background: #f5f5f5;
border-radius: 10px;
font-size: 14px;
color: #999;
display: flex;
align-items: center;
gap: 8px;
}
浮动按钮(FAB)
.fab {
position: absolute;
right: 20px;
bottom: 80px;
width: 56px;
height: 56px;
border-radius: 28px;
background: var(--app-primary);
color: #fff;
font-size: 24px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
border: none;
}
多页面导航模式
多页面原型通过 JS 切换 display 实现,所有页面 HTML 共存于 DOM 中,用 data-page 属性标识。
核心架构:
.page { display: none; }
.page.active { display: flex; flex-direction: column; }
.page { opacity: 0; transition: opacity 0.2s ease; }
.page.active { opacity: 1; }
function goTo(pageId) {
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
document.getElementById(pageId).classList.add('active');
document.querySelectorAll('.tab-item').forEach(t => {
t.classList.toggle('active', t.dataset.page === pageId);
});
}
底部 Tab 切换(主导航):
- 点击 Tab →
goTo('page-xxx') → 同时高亮当前 Tab
- Tab Bar 固定在 phone-frame 底部,所有页面共享
页面内跳转(子页面/详情页):
- 列表项点击 →
goTo('page-detail') → 顶部 nav 显示返回按钮
- 返回按钮 →
goTo('page-xxx') 回到来源页
页面结构规范:
<div class="phone-frame">
<div class="page active" id="page-home">
</div>
<div class="page" id="page-discover">...</div>
<div class="page" id="page-profile">...</div>
<div class="page" id="page-detail">...</div>
</div>
页面布局(每个 page 内部):
.page {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
flex-direction: column;
overflow: hidden;
}
.page-header { flex-shrink: 0; }
.page-body { flex: 1; overflow-y: auto; -webkit-overflow-scrolling: touch; }
.page-footer { flex-shrink: 0; }
注意:
- 页面数量控制在 3–5 个,不要太多(代码量会爆)
- 底部 Tab Bar 只在主页面显示(一般 3–5 个 tab),详情页/子页面不显示 Tab Bar
- 首页默认
class="page active",其余页面无 active
- 代码量可放宽到 400 行以内(多页面场景)
原型模板:多页手机 App
<style>
:root { --app-primary: #4F46E5; --app-primary-light: #EEF2FF; --app-bg: #f5f5f5; }
* { box-sizing: border-box; }
.phone-frame {
width: 375px; height: 720px; margin: 0 auto;
background: var(--app-bg); border-radius: 20px;
overflow: hidden; border: 1px solid #e5e5e5;
position: relative;
}
.page { display: none; position: absolute; top: 0; left: 0; right: 0; bottom: 0; flex-direction: column; opacity: 0; transition: opacity 0.2s ease; }
.page.active { display: flex; opacity: 1; }
.status-bar { display: flex; justify-content: space-between; padding: 10px 20px 6px; font-size: 12px; font-weight: 600; background: #fff; flex-shrink: 0; }
.nav-bar { display: flex; align-items: center; padding: 4px 16px 12px; background: #fff; flex-shrink: 0; }
.nav-back { font-size: 22px; cursor: pointer; margin-right: 8px; color: var(--app-primary); border: none; background: none; padding: 4px; }
.nav-title { font-size: 17px; font-weight: 600; flex: 1; }
.nav-title.large { font-size: 28px; font-weight: 700; }
.nav-action { font-size: 20px; cursor: pointer; }
.page-body { flex: 1; overflow-y: auto; -webkit-overflow-scrolling: touch; }
.tab-bar { display: flex; background: #fff; border-top: 1px solid #f0f0f0; padding: 6px 0 20px; flex-shrink: 0; }
.tab-item { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 2px; font-size: 10px; color: #999; cursor: pointer; border: none; background: none; padding: 4px; }
.tab-item.active { color: var(--app-primary); }
.tab-icon { font-size: 20px; line-height: 1; }
</style>
<div class="phone-frame">
<div class="page active" id="page-home">
<div class="status-bar"><span>9:41</span><span>● ● ●</span></div>
<div class="nav-bar">
<div class="nav-title large">首页</div>
<div class="nav-action">🔔</div>
</div>
<div class="page-body">
</div>
<div class="tab-bar">
<button class="tab-item active" data-page="page-home" onclick="goTo('page-home')">
<span class="tab-icon">◉</span>首页
</button>
<button class="tab-item" data-page="page-discover" onclick="goTo('page-discover')">
<span class="tab-icon">⌕</span>发现
</button>
<button class="tab-item" data-page="page-profile" onclick="goTo('page-profile')">
<span class="tab-icon">☺</span>我的
</button>
</div>
</div>
<div class="page" id="page-discover">
<div class="status-bar"><span>9:41</span><span>● ● ●</span></div>
<div class="nav-bar"><div class="nav-title large">发现</div></div>
<div class="page-body">
</div>
<div class="tab-bar">
<button class="tab-item" data-page="page-home" onclick="goTo('page-home')">
<span class="tab-icon">◉</span>首页
</button>
<button class="tab-item active" data-page="page-discover" onclick="goTo('page-discover')">
<span class="tab-icon">⌕</span>发现
</button>
<button class="tab-item" data-page="page-profile" onclick="goTo('page-profile')">
<span class="tab-icon">☺</span>我的
</button>
</div>
</div>
<div class="page" id="page-detail">
<div class="status-bar"><span>9:41</span><span>● ● ●</span></div>
<div class="nav-bar">
<button class="nav-back" onclick="goTo('page-home')">‹</button>
<div class="nav-title">详情</div>
<div class="nav-action">↗</div>
</div>
<div class="page-body">
</div>
</div>
</div>
<script>
function goTo(pageId) {
document.querySelectorAll('.page').forEach(function(p) { p.classList.remove('active'); });
document.getElementById(pageId).classList.add('active');
document.querySelectorAll('.tab-item').forEach(function(t) {
t.classList.toggle('active', t.dataset.page === pageId);
});
}
</script>
原型模板:单页手机 App
<style>
:root { --app-primary: #4F46E5; --app-primary-light: #EEF2FF; }
.phone-frame { width: 375px; margin: 0 auto; background: #fff; border-radius: 20px; overflow: hidden; border: 1px solid #e5e5e5; position: relative; min-height: 680px; }
.status-bar { display: flex; justify-content: space-between; padding: 10px 20px 6px; font-size: 12px; font-weight: 600; }
.nav-bar { display: flex; align-items: center; justify-content: space-between; padding: 4px 16px 12px; }
.nav-title { font-size: 28px; font-weight: 700; }
.nav-action { font-size: 20px; cursor: pointer; }
</style>
<div class="phone-frame">
<div class="status-bar">
<span>9:41</span>
<span>● ● ●</span>
</div>
<div class="nav-bar">
<div class="nav-title">标题</div>
<div class="nav-action">⊕</div>
</div>
</div>
原型中的图标
不用引入图标库,使用 Unicode/Emoji 字符模拟:
| 场景 | 字符 |
|---|
| 首页 | ◉ |
| 搜索 | ⌕ |
| 消息 | ✉ |
| 我的 | ☺ |
| 设置 | ⚙ |
| 返回 | ‹ |
| 更多 | ⋯ |
| 添加 | ⊕ |
| 关闭 | ✕ |
| 勾选 | ✓ |
| 收藏 | ☆ / ★ |
| 分享 | ↗ |
| 通知 | 🔔 |
| 图片 | 🏞 |
| 购物车 | 🛒 |
| 点赞 | ♡ / ♥ |
原型注意事项
- 原型中允许使用渐变和阴影(与普通 widget 不同),因为原型不需要流式预览效果
- 头像用渐变色块 + 首字母替代真实图片
- 图片占位用纯色矩形 + emoji 图标(如
🏞)
- 内容文字用逼真但虚构的数据,不要用 "Lorem ipsum" 或 "测试文字"
- 底部留出安全区域(
padding-bottom: 20px)模拟 iPhone Home Indicator
注意事项
- 所有数据内联:直接写在 JS 中,不要 fetch
- 初始状态有意义:加载后立即展示内容,不要空白等用户操作
- 交互反馈:可点击元素必须有 hover/active 视觉反馈
- 中文优先:UI 文字使用中文
- 简洁代码:普通组件控制在 200 行以内,多页面原型可放宽到 400 行,不要过度工程化
- 流式友好:style 在前,HTML 在中,script 在后——用户先看到结构,最后才激活交互