feat: improve Banban UI and binding diagnostics
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
# 伴伴小程序当前页面全景
|
||||
|
||||
本目录中的 `banban-page-state-overview.png` 根据当前 `banban-mini` 构建生成,包含 11 个页面路由的 26 张真实微信开发者工具截图。
|
||||
|
||||
## 内容来源
|
||||
|
||||
- 页面主体全部来自微信开发者工具中的实际小程序页面,不使用 HTML 重画小程序界面。
|
||||
- 游客态直接由当前构建生成。
|
||||
- 登录态使用本地固定展示数据驱动当前页面组件,不连接生产接口,也不包含真实用户数据。
|
||||
- `actual-screens/` 保留总览中使用的原始截图。
|
||||
|
||||
## 重新渲染
|
||||
|
||||
在 macOS 上运行:
|
||||
|
||||
```bash
|
||||
CLANG_MODULE_CACHE_PATH=/tmp/banban-clang-cache clang -fobjc-arc \
|
||||
-framework AppKit -framework WebKit render-overview.m \
|
||||
-o /tmp/banban-overview-renderer
|
||||
/tmp/banban-overview-renderer overview.html banban-page-state-overview.png
|
||||
```
|
||||
|
||||
`overview.html` 和 `overview.css` 只负责截图编排、分组标题和状态说明,不模拟小程序组件。
|
||||
@@ -0,0 +1,647 @@
|
||||
@import url("./shared.css");
|
||||
|
||||
.system-poster {
|
||||
min-height: 2520px;
|
||||
background: #fffdf9;
|
||||
}
|
||||
|
||||
.system-header {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 360px;
|
||||
gap: 50px;
|
||||
min-height: 300px;
|
||||
align-items: center;
|
||||
padding: 58px 64px 52px;
|
||||
border-bottom: 1px solid #eee8df;
|
||||
}
|
||||
|
||||
.brand-lockup {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.brand-mark {
|
||||
display: flex;
|
||||
width: 122px;
|
||||
height: 122px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 28px;
|
||||
background: linear-gradient(135deg, var(--orange), var(--orange-deep));
|
||||
box-shadow: 0 18px 36px rgba(255, 140, 66, 0.26);
|
||||
font-size: 64px;
|
||||
}
|
||||
|
||||
.brand-note {
|
||||
max-width: 180px;
|
||||
}
|
||||
|
||||
.brand-note strong {
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.brand-note span {
|
||||
color: var(--muted);
|
||||
font-size: 14px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.system-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.system-main {
|
||||
padding: 44px 42px 52px 54px;
|
||||
border-right: 1px solid #eee8df;
|
||||
}
|
||||
|
||||
.system-aside {
|
||||
padding: 44px 54px 52px 42px;
|
||||
background: #fbfaf7;
|
||||
}
|
||||
|
||||
.spec-section {
|
||||
padding: 30px 0 34px;
|
||||
border-bottom: 1px solid #ece8e2;
|
||||
}
|
||||
|
||||
.spec-section:first-child { padding-top: 0; }
|
||||
.spec-section:last-child { border-bottom: 0; }
|
||||
|
||||
.spec-heading {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.spec-heading h2 {
|
||||
margin: 0;
|
||||
font-size: 25px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.spec-heading span {
|
||||
color: var(--quiet);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.button-showcase {
|
||||
display: grid;
|
||||
grid-template-columns: 1.45fr 1fr;
|
||||
gap: 20px 24px;
|
||||
}
|
||||
|
||||
.control-sample {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.sample-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 9px;
|
||||
color: #4b5563;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.sample-label code {
|
||||
color: var(--quiet);
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.ds-btn {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 62px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
border: 0;
|
||||
border-radius: 14px;
|
||||
background: var(--orange);
|
||||
color: #ffffff;
|
||||
font-size: 19px;
|
||||
font-weight: 760;
|
||||
box-shadow: 0 10px 24px rgba(255, 140, 66, 0.24);
|
||||
}
|
||||
|
||||
.ds-btn.compact {
|
||||
width: 214px;
|
||||
height: 54px;
|
||||
margin: 0 auto;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.ds-btn.green { background: #07c160; box-shadow: 0 10px 24px rgba(7, 193, 96, 0.18); }
|
||||
.ds-btn.dark { background: #1f2937; box-shadow: 0 10px 24px rgba(31, 41, 55, 0.18); }
|
||||
.ds-btn.pressed { transform: translateY(2px); background: var(--orange-deep); box-shadow: 0 4px 10px rgba(255, 107, 53, 0.22); }
|
||||
.ds-btn.focused { outline: 3px solid rgba(255, 140, 66, 0.32); outline-offset: 3px; }
|
||||
.ds-btn.disabled { background: #e5e7eb; color: #a9afb7; box-shadow: none; }
|
||||
.ds-btn.loading { background: #f2f0ed; color: #929292; box-shadow: none; }
|
||||
|
||||
.spinner {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border: 2px solid rgba(146, 146, 146, 0.35);
|
||||
border-top-color: #929292;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.state-strip {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.state-cell .ds-btn {
|
||||
height: 50px;
|
||||
font-size: 15px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.audio-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.audio-btn {
|
||||
display: flex;
|
||||
height: 58px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
border: 1px solid #ffd0b1;
|
||||
border-radius: 14px;
|
||||
background: #fffaf6;
|
||||
color: var(--orange-deep);
|
||||
font-size: 16px;
|
||||
font-weight: 730;
|
||||
}
|
||||
|
||||
.audio-btn.playing {
|
||||
border-color: var(--orange);
|
||||
background: var(--orange-soft);
|
||||
}
|
||||
|
||||
.audio-icon {
|
||||
display: flex;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
background: var(--orange);
|
||||
color: #ffffff;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.secondary-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.secondary-btn {
|
||||
display: flex;
|
||||
height: 54px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid #ffb786;
|
||||
border-radius: 13px;
|
||||
background: #ffffff;
|
||||
color: var(--orange-deep);
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.secondary-btn.neutral { border-color: #d8dde4; color: #4b5563; }
|
||||
.secondary-btn.danger { border-color: #e7a4b2; color: var(--red); }
|
||||
|
||||
.text-actions {
|
||||
display: flex;
|
||||
gap: 48px;
|
||||
margin-top: 22px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.text-action {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: var(--orange-deep);
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.icon-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(8, 1fr);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.icon-sample {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.round-icon {
|
||||
display: flex;
|
||||
width: 54px;
|
||||
height: 54px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto 8px;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 50%;
|
||||
background: #ffffff;
|
||||
color: #374151;
|
||||
font-size: 22px;
|
||||
box-shadow: 0 4px 12px rgba(31, 41, 55, 0.06);
|
||||
}
|
||||
|
||||
.icon-sample span:last-child {
|
||||
color: var(--muted);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.compound-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1.3fr 1fr;
|
||||
gap: 26px;
|
||||
}
|
||||
|
||||
.segment {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
overflow: hidden;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 13px;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.segment span {
|
||||
display: flex;
|
||||
height: 52px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-right: 1px solid #eeeeee;
|
||||
color: #4b5563;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.segment span:last-child { border-right: 0; }
|
||||
.segment .active { background: var(--orange); color: #ffffff; font-weight: 750; }
|
||||
|
||||
.switch-row,
|
||||
.slider-row {
|
||||
display: flex;
|
||||
min-height: 48px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 18px;
|
||||
color: #4b5563;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.switch {
|
||||
position: relative;
|
||||
width: 54px;
|
||||
height: 30px;
|
||||
border-radius: 999px;
|
||||
background: var(--orange);
|
||||
}
|
||||
|
||||
.switch::after {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 50%;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 2px 6px rgba(31, 41, 55, 0.18);
|
||||
content: "";
|
||||
}
|
||||
|
||||
.slider {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
height: 7px;
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(90deg, var(--orange) 0 62%, #e5e7eb 62% 100%);
|
||||
}
|
||||
|
||||
.slider::after {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 62%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
transform: translate(-50%, -50%);
|
||||
border: 3px solid #ffffff;
|
||||
border-radius: 50%;
|
||||
background: var(--orange);
|
||||
box-shadow: 0 2px 8px rgba(31, 41, 55, 0.18);
|
||||
content: "";
|
||||
}
|
||||
|
||||
.bottom-nav-demo {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
max-width: 650px;
|
||||
margin: 0 auto;
|
||||
padding: 14px 20px 10px;
|
||||
border: 1px solid #ececec;
|
||||
border-radius: 18px 18px 8px 8px;
|
||||
background: #ffffff;
|
||||
box-shadow: var(--shadow-soft);
|
||||
}
|
||||
|
||||
.feedback-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.status-chip-demo {
|
||||
display: flex;
|
||||
min-height: 48px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 7px;
|
||||
border-radius: 999px;
|
||||
background: #f3f4f6;
|
||||
color: #4b5563;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.status-chip-demo::before {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: currentColor;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.status-chip-demo.online { background: #e8f8f0; color: #15803d; }
|
||||
.status-chip-demo.syncing { background: #e8f0fe; color: #2563eb; }
|
||||
.status-chip-demo.sleeping { background: #f0e6ff; color: #7c3aed; }
|
||||
.status-chip-demo.low { background: #fff7e8; color: #c2410c; }
|
||||
.status-chip-demo.alarm { background: #fff1f2; color: #be123c; }
|
||||
|
||||
.feedback-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 14px;
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
.feedback-banner {
|
||||
display: flex;
|
||||
min-height: 56px;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 0 16px;
|
||||
border-left: 4px solid #16a34a;
|
||||
border-radius: 0 10px 10px 0;
|
||||
background: #f0fdf4;
|
||||
color: #14532d;
|
||||
font-size: 14px;
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
.feedback-banner.warning {
|
||||
border-left-color: var(--orange);
|
||||
background: #fff7ed;
|
||||
color: #9a3412;
|
||||
}
|
||||
|
||||
.card-demo-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1.15fr 1fr;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.status-card-demo {
|
||||
min-height: 164px;
|
||||
padding: 22px;
|
||||
border-radius: 18px;
|
||||
background: linear-gradient(135deg, var(--orange), var(--orange-deep));
|
||||
color: #ffffff;
|
||||
box-shadow: 0 14px 28px rgba(255, 140, 66, 0.22);
|
||||
}
|
||||
|
||||
.status-card-demo .demo-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 10px;
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 255, 255, 0.18);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.status-card-demo strong {
|
||||
display: block;
|
||||
margin-top: 18px;
|
||||
font-size: 38px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.status-card-demo span:last-child {
|
||||
display: block;
|
||||
margin-top: 7px;
|
||||
color: rgba(255, 255, 255, 0.82);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.empty-card-demo {
|
||||
display: flex;
|
||||
min-height: 164px;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
border: 1px solid #ececec;
|
||||
border-radius: 18px;
|
||||
background: #ffffff;
|
||||
text-align: center;
|
||||
box-shadow: var(--shadow-soft);
|
||||
}
|
||||
|
||||
.empty-card-demo b {
|
||||
margin: 8px 0 4px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.empty-card-demo span {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.nav-demo-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
color: #999999;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.nav-demo-item.active { color: var(--orange); font-weight: 760; }
|
||||
|
||||
.nav-demo-item img {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.aside-panel {
|
||||
padding: 28px 0 32px;
|
||||
border-bottom: 1px solid #e9e4dc;
|
||||
}
|
||||
|
||||
.aside-panel:first-child { padding-top: 0; }
|
||||
.aside-panel:last-child { border-bottom: 0; }
|
||||
|
||||
.aside-panel h2 {
|
||||
margin: 0 0 18px;
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.rule-list {
|
||||
display: grid;
|
||||
gap: 13px;
|
||||
}
|
||||
|
||||
.rule {
|
||||
display: grid;
|
||||
grid-template-columns: 24px 1fr;
|
||||
gap: 10px;
|
||||
align-items: start;
|
||||
color: #4b5563;
|
||||
font-size: 14px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.rule-mark {
|
||||
display: flex;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
background: var(--orange);
|
||||
color: #ffffff;
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.token-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 16px 12px;
|
||||
}
|
||||
|
||||
.color-token {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.swatch {
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
margin-bottom: 8px;
|
||||
border: 1px solid rgba(31, 41, 55, 0.06);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.color-token code {
|
||||
display: block;
|
||||
color: #4b5563;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.color-token span {
|
||||
color: var(--quiet);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.spec-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.spec-table td {
|
||||
padding: 11px 0;
|
||||
border-bottom: 1px solid #ebe7e1;
|
||||
color: #4b5563;
|
||||
font-size: 13px;
|
||||
line-height: 1.45;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.spec-table tr:last-child td { border-bottom: 0; }
|
||||
.spec-table td:first-child { width: 42%; color: var(--quiet); }
|
||||
.spec-table td:last-child { color: var(--ink); font-weight: 650; }
|
||||
|
||||
.type-sample {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.type-sample:last-child { margin-bottom: 0; }
|
||||
|
||||
.type-sample strong {
|
||||
display: block;
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.type-sample span {
|
||||
color: var(--quiet);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.type-display strong { font-size: 32px; }
|
||||
.type-title strong { font-size: 22px; }
|
||||
.type-body strong { font-size: 16px; }
|
||||
.type-caption strong { font-size: 13px; }
|
||||
|
||||
.spacing-scale {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.space-row {
|
||||
display: grid;
|
||||
grid-template-columns: 42px 1fr 52px;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.space-bar {
|
||||
height: 10px;
|
||||
border-radius: 3px;
|
||||
background: var(--orange-soft);
|
||||
}
|
||||
|
||||
.inventory-note {
|
||||
padding: 16px;
|
||||
border-left: 4px solid var(--orange);
|
||||
border-radius: 0 8px 8px 0;
|
||||
background: #fff7ed;
|
||||
color: #9a3412;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=1440, initial-scale=1">
|
||||
<title>伴伴 · 交互控件现状规范</title>
|
||||
<link rel="stylesheet" href="./design-system.css">
|
||||
</head>
|
||||
<body>
|
||||
<main class="poster system-poster">
|
||||
<header class="system-header">
|
||||
<div>
|
||||
<div class="poster-kicker">BANBAN MINI PROGRAM · UI INVENTORY</div>
|
||||
<h1 class="poster-title">伴伴 · 交互控件现状规范</h1>
|
||||
<p class="poster-subtitle">从现有页面 TSX、SCSS 与 tabBar 配置提取。以真实实现为准,不将参考图的视觉语言替换进产品。</p>
|
||||
<div style="display:flex;gap:10px;margin-top:24px">
|
||||
<span class="meta-chip">当前实现</span>
|
||||
<span class="meta-chip">750rpx 设计稿</span>
|
||||
<span class="meta-chip">微信小程序</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="brand-lockup">
|
||||
<div class="brand-mark">🤖</div>
|
||||
<div class="brand-note"><strong>让关爱随时随地</strong><span>设备、家庭和陪伴体验中的温暖反馈。</span></div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="system-layout">
|
||||
<section class="system-main">
|
||||
<div class="spec-section">
|
||||
<div class="spec-heading"><h2>1. 主要操作</h2><span>页面主任务 · 每个视图只保留一个视觉主按钮</span></div>
|
||||
<div class="button-showcase">
|
||||
<div class="control-sample"><div class="sample-label"><span>橙色主操作</span><code>88–96rpx</code></div><button class="ds-btn">确认并发送绑卡指令</button></div>
|
||||
<div class="control-sample"><div class="sample-label"><span>紧凑主操作</span><code>88rpx</code></div><button class="ds-btn compact">保存休眠时间</button></div>
|
||||
<div class="control-sample"><div class="sample-label"><span>微信登录</span><code>#07C160</code></div><button class="ds-btn green">登录后继续</button></div>
|
||||
<div class="control-sample"><div class="sample-label"><span>设备控制</span><code>#1F2937</code></div><button class="ds-btn compact dark">进入休眠</button></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="spec-section">
|
||||
<div class="spec-heading"><h2>2. 状态展示</h2><span>以“保存”为例</span></div>
|
||||
<div class="state-strip">
|
||||
<div class="state-cell"><div class="sample-label"><span>默认</span></div><button class="ds-btn">保存</button></div>
|
||||
<div class="state-cell"><div class="sample-label"><span>按下</span></div><button class="ds-btn pressed">保存</button></div>
|
||||
<div class="state-cell"><div class="sample-label"><span>聚焦</span></div><button class="ds-btn focused">保存</button></div>
|
||||
<div class="state-cell"><div class="sample-label"><span>禁用</span></div><button class="ds-btn disabled">保存</button></div>
|
||||
<div class="state-cell"><div class="sample-label"><span>加载</span></div><button class="ds-btn loading"><i class="spinner"></i>处理中</button></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="spec-section">
|
||||
<div class="spec-heading"><h2>3. 音频与留言操作</h2><span>固定高度,避免播放状态改变布局</span></div>
|
||||
<div class="audio-grid">
|
||||
<div class="audio-btn"><span class="audio-icon">▶</span>播放留言</div>
|
||||
<div class="audio-btn"><span class="audio-icon">↻</span>再次播放</div>
|
||||
<div class="audio-btn playing"><span class="audio-icon">Ⅱ</span>播放中 <span class="audio-wave"><i></i><i></i><i></i><i></i><i></i></span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="spec-section">
|
||||
<div class="spec-heading"><h2>4. 次要、文字与危险操作</h2><span>降低层级,不与主任务争夺注意力</span></div>
|
||||
<div class="secondary-grid">
|
||||
<div class="secondary-btn">稍后设置</div>
|
||||
<div class="secondary-btn neutral">取消</div>
|
||||
<div class="secondary-btn danger">解除绑定</div>
|
||||
</div>
|
||||
<div class="text-actions">
|
||||
<div class="text-action">查看往期来信 <b>›</b></div>
|
||||
<div class="text-action">管理设备 <b>›</b></div>
|
||||
<div class="text-action">暂不登录,先浏览</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="spec-section">
|
||||
<div class="spec-heading"><h2>5. 圆形图标按钮</h2><span>72–80rpx · 熟悉符号优先</span></div>
|
||||
<div class="icon-grid">
|
||||
<div class="icon-sample"><div class="round-icon">‹</div><span>返回</span></div>
|
||||
<div class="icon-sample"><div class="round-icon">×</div><span>关闭</span></div>
|
||||
<div class="icon-sample"><div class="round-icon">•••</div><span>更多</span></div>
|
||||
<div class="icon-sample"><div class="round-icon">▶</div><span>播放</span></div>
|
||||
<div class="icon-sample"><div class="round-icon">↻</div><span>刷新</span></div>
|
||||
<div class="icon-sample"><div class="round-icon">✉</div><span>留言</span></div>
|
||||
<div class="icon-sample"><div class="round-icon">⌁</div><span>绑定</span></div>
|
||||
<div class="icon-sample"><div class="round-icon">⚙</div><span>设置</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="spec-section">
|
||||
<div class="spec-heading"><h2>6. 复合控件</h2><span>分段、开关与音量</span></div>
|
||||
<div class="compound-grid">
|
||||
<div>
|
||||
<div class="sample-label"><span>分段控制</span><code>68rpx</code></div>
|
||||
<div class="segment"><span class="active">当前位置</span><span>今日轨迹</span><span>最近点位</span><span>全部</span></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="switch-row"><span>允许家庭通知</span><i class="switch"></i></div>
|
||||
<div class="slider-row"><span>音量</span><i class="slider"></i><strong>60%</strong></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="spec-section">
|
||||
<div class="spec-heading"><h2>7. 底部导航</h2><span>来自现有 app.config.ts 与 tab-icons</span></div>
|
||||
<div class="bottom-nav-demo">
|
||||
<div class="nav-demo-item active"><img src="../../banban-mini/src/assets/tab-icons/device-selected.png" alt=""><span>首页</span></div>
|
||||
<div class="nav-demo-item"><img src="../../banban-mini/src/assets/tab-icons/chat-icon.png" alt=""><span>玩伴</span></div>
|
||||
<div class="nav-demo-item"><img src="../../banban-mini/src/assets/tab-icons/location-icon.png" alt=""><span>定位</span></div>
|
||||
<div class="nav-demo-item"><img src="../../banban-mini/src/assets/tab-icons/manage-icon.png" alt=""><span>管理</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="spec-section">
|
||||
<div class="spec-heading"><h2>8. 状态标签与反馈</h2><span>设备状态、告警和操作结果</span></div>
|
||||
<div class="feedback-grid">
|
||||
<div class="status-chip-demo online">在线</div>
|
||||
<div class="status-chip-demo syncing">同步中</div>
|
||||
<div class="status-chip-demo sleeping">休眠中</div>
|
||||
<div class="status-chip-demo low">低电量</div>
|
||||
<div class="status-chip-demo alarm">紧急告警</div>
|
||||
</div>
|
||||
<div class="feedback-row">
|
||||
<div class="feedback-banner">✓ 休眠时间已发送</div>
|
||||
<div class="feedback-banner warning">! 设备暂未确认,请稍后查看</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="spec-section">
|
||||
<div class="spec-heading"><h2>9. 状态卡片与空状态</h2><span>信息概览和下一步引导</span></div>
|
||||
<div class="card-demo-grid">
|
||||
<div class="status-card-demo"><span class="demo-badge">● 已完成绑定</span><strong>82%</strong><span>小雨的伴伴 · 在线</span></div>
|
||||
<div class="empty-card-demo"><div style="font-size:32px">🤖</div><b>先绑定一台伴伴设备</b><span>绑定后查看设备状态</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<aside class="system-aside">
|
||||
<div class="aside-panel">
|
||||
<h2>使用原则</h2>
|
||||
<div class="rule-list">
|
||||
<div class="rule"><span class="rule-mark">1</span><span>每个视图保持一个明确的主操作,橙色用于产品主任务。</span></div>
|
||||
<div class="rule"><span class="rule-mark">2</span><span>微信授权沿用微信绿色,不强行改成品牌橙色。</span></div>
|
||||
<div class="rule"><span class="rule-mark">3</span><span>设备休眠等硬件动作使用深色或语义色,和普通保存区分。</span></div>
|
||||
<div class="rule"><span class="rule-mark">4</span><span>危险操作采用红色描边,保留确认步骤,降低误触风险。</span></div>
|
||||
<div class="rule"><span class="rule-mark">5</span><span>加载与禁用状态保持固定尺寸,动态文字不引起页面跳动。</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="aside-panel">
|
||||
<h2>颜色体系</h2>
|
||||
<div class="token-grid">
|
||||
<div class="color-token"><div class="swatch" style="background:#ff8c42"></div><code>#FF8C42</code><span>品牌橙</span></div>
|
||||
<div class="color-token"><div class="swatch" style="background:#ff6b35"></div><code>#FF6B35</code><span>强调橙</span></div>
|
||||
<div class="color-token"><div class="swatch" style="background:#1a1a1a"></div><code>#1A1A1A</code><span>主文字</span></div>
|
||||
<div class="color-token"><div class="swatch" style="background:#f5f7fa"></div><code>#F5F7FA</code><span>页面底色</span></div>
|
||||
<div class="color-token"><div class="swatch" style="background:#07c160"></div><code>#07C160</code><span>微信绿</span></div>
|
||||
<div class="color-token"><div class="swatch" style="background:#16a34a"></div><code>#16A34A</code><span>成功绿</span></div>
|
||||
<div class="color-token"><div class="swatch" style="background:#5b8ff9"></div><code>#5B8FF9</code><span>信息蓝</span></div>
|
||||
<div class="color-token"><div class="swatch" style="background:#8b5cf6"></div><code>#8B5CF6</code><span>角色紫</span></div>
|
||||
<div class="color-token"><div class="swatch" style="background:#be123c"></div><code>#BE123C</code><span>告警红</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="aside-panel">
|
||||
<h2>现有规格</h2>
|
||||
<table class="spec-table">
|
||||
<tr><td>主要按钮高度</td><td>88–100rpx</td></tr>
|
||||
<tr><td>紧凑按钮高度</td><td>60–88rpx</td></tr>
|
||||
<tr><td>主要按钮圆角</td><td>16–22rpx</td></tr>
|
||||
<tr><td>页面卡片圆角</td><td>20–28rpx</td></tr>
|
||||
<tr><td>圆形按钮</td><td>72–80rpx</td></tr>
|
||||
<tr><td>按钮水平内边距</td><td>24–32rpx</td></tr>
|
||||
<tr><td>卡片阴影</td><td>0 2rpx 12rpx / 4–8%</td></tr>
|
||||
<tr><td>控件垂直间距</td><td>16–24rpx</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="aside-panel">
|
||||
<h2>字体层级</h2>
|
||||
<div class="type-sample type-display"><strong>页面标题</strong><span>44–52rpx · 700/800</span></div>
|
||||
<div class="type-sample type-title"><strong>卡片标题与关键数值</strong><span>30–36rpx · 650/750</span></div>
|
||||
<div class="type-sample type-body"><strong>正文与按钮文字</strong><span>26–32rpx · 400/700</span></div>
|
||||
<div class="type-sample type-caption"><strong>辅助信息、时间与状态</strong><span>22–24rpx · 400/600</span></div>
|
||||
</div>
|
||||
|
||||
<div class="aside-panel">
|
||||
<h2>间距基准</h2>
|
||||
<div class="spacing-scale">
|
||||
<div class="space-row"><span>XS</span><div class="space-bar" style="width:20%"></div><strong>8rpx</strong></div>
|
||||
<div class="space-row"><span>SM</span><div class="space-bar" style="width:35%"></div><strong>16rpx</strong></div>
|
||||
<div class="space-row"><span>MD</span><div class="space-bar" style="width:50%"></div><strong>24rpx</strong></div>
|
||||
<div class="space-row"><span>LG</span><div class="space-bar" style="width:68%"></div><strong>32rpx</strong></div>
|
||||
<div class="space-row"><span>XL</span><div class="space-bar" style="width:100%"></div><strong>48rpx</strong></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="aside-panel">
|
||||
<h2>现状说明</h2>
|
||||
<div class="inventory-note">当前按钮尚未收敛为统一组件:登录、绑定、定位、管理页面存在 88、92、96、100rpx 等多种高度。这张图记录真实实现,不代表已经完成组件统一。</div>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<footer class="poster-footer">
|
||||
<span class="footer-brand">伴伴 · 交互控件现状规范</span>
|
||||
<span>Source: app.config.ts + pages/**/*.scss · 2026.08</span>
|
||||
</footer>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,145 @@
|
||||
@import url("./overview.css");
|
||||
|
||||
html,
|
||||
body.atlas-original-resolution,
|
||||
.atlas-original-resolution .atlas {
|
||||
width: 430px;
|
||||
}
|
||||
|
||||
.atlas-original-resolution .atlas-header {
|
||||
display: block;
|
||||
min-height: 0;
|
||||
padding: 34px 24px 30px;
|
||||
}
|
||||
|
||||
.atlas-original-resolution .eyebrow {
|
||||
margin-bottom: 8px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.atlas-original-resolution h1 {
|
||||
font-size: 31px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.atlas-original-resolution .header-copy > p {
|
||||
margin-top: 14px;
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.atlas-original-resolution .resolution-badge {
|
||||
display: inline-flex;
|
||||
margin-top: 14px;
|
||||
padding: 7px 10px;
|
||||
border: 1px solid #f1c79f;
|
||||
border-radius: 6px;
|
||||
background: #fff4e8;
|
||||
color: #a85d25;
|
||||
font-size: 12px;
|
||||
font-weight: 750;
|
||||
}
|
||||
|
||||
.atlas-original-resolution .legend {
|
||||
gap: 8px 14px;
|
||||
margin-top: 18px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.atlas-original-resolution .metrics {
|
||||
margin-top: 24px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.atlas-original-resolution .metric {
|
||||
min-height: 82px;
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.atlas-original-resolution .metric strong {
|
||||
margin-bottom: 5px;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.atlas-original-resolution .metric span {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.atlas-original-resolution .flow {
|
||||
padding: 38px 19px 48px;
|
||||
}
|
||||
|
||||
.atlas-original-resolution .flow-heading {
|
||||
display: block;
|
||||
min-height: 0;
|
||||
margin: 0 2px 24px;
|
||||
}
|
||||
|
||||
.atlas-original-resolution .flow-number {
|
||||
top: -16px;
|
||||
font-size: 54px;
|
||||
}
|
||||
|
||||
.atlas-original-resolution .flow-eyebrow {
|
||||
margin-bottom: 7px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.atlas-original-resolution h2 {
|
||||
font-size: 26px;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.atlas-original-resolution .flow-heading > p {
|
||||
max-width: none;
|
||||
margin-top: 12px;
|
||||
font-size: 12px;
|
||||
line-height: 1.65;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.atlas-original-resolution .state-grid {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.atlas-original-resolution .state-card {
|
||||
box-shadow: 0 7px 18px rgba(70, 52, 37, 0.09);
|
||||
}
|
||||
|
||||
.atlas-original-resolution .state-card img {
|
||||
image-rendering: auto;
|
||||
}
|
||||
|
||||
.atlas-original-resolution .state-copy {
|
||||
grid-template-columns: 38px minmax(0, 1fr);
|
||||
gap: 12px;
|
||||
min-height: 104px;
|
||||
padding: 17px;
|
||||
}
|
||||
|
||||
.atlas-original-resolution .state-copy > b {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.atlas-original-resolution .state-copy h3 {
|
||||
margin-bottom: 6px;
|
||||
font-size: 19px;
|
||||
}
|
||||
|
||||
.atlas-original-resolution .state-copy p {
|
||||
font-size: 14px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.atlas-original-resolution footer {
|
||||
min-height: 130px;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
padding: 24px;
|
||||
font-size: 10px;
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>伴伴小程序当前页面全景 · 原始像素版</title>
|
||||
<link rel="stylesheet" href="overview-original-resolution.css">
|
||||
</head>
|
||||
<body class="atlas-original-resolution">
|
||||
<main class="atlas">
|
||||
<header class="atlas-header">
|
||||
<div class="header-copy">
|
||||
<div class="eyebrow">BANBAN MINI PROGRAM · CURRENT BUILD</div>
|
||||
<h1>伴伴小程序 / 当前页面全景</h1>
|
||||
<div class="resolution-badge">单列原始像素版</div>
|
||||
<p>全部页面画面来自当前 <strong>banban-mini</strong> 构建,并由微信开发者工具直接截图。登录态使用本地固定展示数据,不包含线上用户数据。</p>
|
||||
<div class="legend">
|
||||
<span><i class="dot orange"></i>游客与入口</span>
|
||||
<span><i class="dot green"></i>设备与绑定</span>
|
||||
<span><i class="dot blue"></i>会话与定位</span>
|
||||
<span><i class="dot coral"></i>管理与家庭</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="metrics" aria-label="页面统计">
|
||||
<div class="metric"><strong>11</strong><span>当前路由</span></div>
|
||||
<div class="metric"><strong>26</strong><span>真实截图状态</span></div>
|
||||
<div class="metric"><strong>4</strong><span>核心 Tab</span></div>
|
||||
<div class="metric wide"><strong>390 px</strong><span>微信模拟器原始截图宽度</span></div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="flow flow-entry">
|
||||
<div class="flow-heading">
|
||||
<div>
|
||||
<span class="flow-number">01</span>
|
||||
<p class="flow-eyebrow">ENTRY & DEVICE</p>
|
||||
<h2>进入、登录与设备首页</h2>
|
||||
</div>
|
||||
<p>从游客浏览、登录入口到设备绑定后的状态、告警和设备详情。</p>
|
||||
</div>
|
||||
<div class="state-grid">
|
||||
<article class="state-card"><img src="actual-screens/01-device-guest.png" alt="设备首页游客态"><div class="state-copy"><b>01</b><div><h3>首页 · 游客态</h3><p>未登录时的设备入口</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/02-login.png" alt="登录页"><div class="state-copy"><b>02</b><div><h3>登录 · 默认</h3><p>微信登录与协议确认</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/08-device-online.png" alt="已绑定设备首页"><div class="state-copy"><b>03</b><div><h3>首页 · 已绑定</h3><p>电量、休眠、音量与告警</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/09-device-detail.png" alt="设备详情展开"><div class="state-copy"><b>04</b><div><h3>首页 · 详情展开</h3><p>告警位置与设备详细信息</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/06-notification-invalid.png" alt="通知链接失效"><div class="state-copy"><b>05</b><div><h3>通知 · 链接失效</h3><p>无效通知的回退页面</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/20-bind-scan.png" alt="设备扫码绑定入口"><div class="state-copy"><b>06</b><div><h3>绑定 · 扫码入口</h3><p>扫码或填写设备信息</p></div></div></article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="flow flow-binding">
|
||||
<div class="flow-heading">
|
||||
<div>
|
||||
<span class="flow-number">02</span>
|
||||
<p class="flow-eyebrow">BINDING & DEVICE CONTROL</p>
|
||||
<h2>设备二维码、贴卡与休眠</h2>
|
||||
</div>
|
||||
<p>当前绑定页真实展示二维码识别、等待贴卡、追加卡片和休眠配置。</p>
|
||||
</div>
|
||||
<div class="state-grid">
|
||||
<article class="state-card"><img src="actual-screens/21-bind-recognized.png" alt="设备二维码已识别"><div class="state-copy"><b>07</b><div><h3>绑定 · 已识别</h3><p>设备号和序列号已回填</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/22-bind-waiting-card.png" alt="等待设备贴卡"><div class="state-copy"><b>08</b><div><h3>绑定 · 等待贴卡</h3><p>绑卡指令和倒计时状态</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/23-bind-add-card.png" alt="添加设备卡片"><div class="state-copy"><b>09</b><div><h3>卡片 · 添加入口</h3><p>为当前设备追加新卡</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/24-sleep-schedule.png" alt="定时休眠设置"><div class="state-copy"><b>10</b><div><h3>设备 · 定时休眠</h3><p>休眠时段和时区配置</p></div></div></article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="flow flow-social">
|
||||
<div class="flow-heading">
|
||||
<div>
|
||||
<span class="flow-number">03</span>
|
||||
<p class="flow-eyebrow">MESSAGES & LOCATION</p>
|
||||
<h2>亲子会话与设备定位</h2>
|
||||
</div>
|
||||
<p>覆盖游客预览、会话列表、AI 与家长消息,以及绑定设备后的实时位置。</p>
|
||||
</div>
|
||||
<div class="state-grid">
|
||||
<article class="state-card"><img src="actual-screens/03-chat-guest.png" alt="玩伴游客态"><div class="state-copy"><b>11</b><div><h3>玩伴 · 游客态</h3><p>未登录时的会话能力预览</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/10-chat-list.png" alt="会话列表"><div class="state-copy"><b>12</b><div><h3>玩伴 · 会话列表</h3><p>AI、家长与宝贝会话</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/11-chat-ai-detail.png" alt="AI 会话详情"><div class="state-copy"><b>13</b><div><h3>会话 · AI 玩伴</h3><p>宝贝与伴伴的消息记录</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/12-chat-parent-detail.png" alt="家长会话详情"><div class="state-copy"><b>14</b><div><h3>会话 · 家长沟通</h3><p>文字、语音和留言控制</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/04-location-guest.png" alt="定位游客态"><div class="state-copy"><b>15</b><div><h3>定位 · 游客态</h3><p>地图与位置能力预览</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/13-location-current.png" alt="设备当前位置"><div class="state-copy"><b>16</b><div><h3>定位 · 当前位置</h3><p>地图、地址和定位参数</p></div></div></article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="flow flow-manage">
|
||||
<div class="flow-heading">
|
||||
<div>
|
||||
<span class="flow-number">04</span>
|
||||
<p class="flow-eyebrow">MANAGEMENT & FAMILY</p>
|
||||
<h2>管理、资料与家庭共享</h2>
|
||||
</div>
|
||||
<p>当前管理主页与真实交互弹窗,包含宝贝、卡片、角色、家庭和邀请状态。</p>
|
||||
</div>
|
||||
<div class="state-grid">
|
||||
<article class="state-card"><img src="actual-screens/05-manage-guest.png" alt="管理游客态"><div class="state-copy"><b>17</b><div><h3>管理 · 游客态</h3><p>管理功能的登录前预览</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/14-manage-overview.png" alt="管理总览"><div class="state-copy"><b>18</b><div><h3>管理 · 账号总览</h3><p>账号、设备和系统版本</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/15-manage-menu.png" alt="管理功能菜单"><div class="state-copy"><b>19</b><div><h3>管理 · 功能列表</h3><p>卡片、角色、家庭和通知</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/16-manage-child-modal.png" alt="切换当前宝贝弹窗"><div class="state-copy"><b>20</b><div><h3>宝贝 · 切换资料</h3><p>当前、修改和删除操作</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/17-manage-cards-modal.png" alt="我的卡片弹窗"><div class="state-copy"><b>21</b><div><h3>卡片 · 卡片列表</h3><p>卡片名称、UUID 和编辑</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/18-manage-role-modal.png" alt="切换 AI 角色弹窗"><div class="state-copy"><b>22</b><div><h3>角色 · AI 角色</h3><p>当前角色和可选角色</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/19-manage-family-modal.png" alt="家庭成员弹窗"><div class="state-copy"><b>23</b><div><h3>家庭 · 成员列表</h3><p>主成员、共享成员和邀请</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/25-family-invite-valid.png" alt="有效家庭邀请"><div class="state-copy"><b>24</b><div><h3>邀请 · 等待加入</h3><p>有效家庭共享邀请</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/26-family-invite-expired.png" alt="过期家庭邀请"><div class="state-copy"><b>25</b><div><h3>邀请 · 已失效</h3><p>过期邀请的禁用状态</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/07-family-invite-invalid.png" alt="未登录邀请跳转登录"><div class="state-copy"><b>26</b><div><h3>邀请 · 未登录跳转</h3><p>进入邀请前的登录承接页</p></div></div></article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<span>Generated from the current banban-mini build</span>
|
||||
<span>WeChat DevTools screenshots · local fixtures only · 2026-09-09</span>
|
||||
</footer>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,80 @@
|
||||
@import url("./overview.css");
|
||||
|
||||
.atlas-readable .atlas-header {
|
||||
min-height: 430px;
|
||||
grid-template-columns: minmax(0, 1fr) 420px;
|
||||
padding: 68px 76px;
|
||||
background: #fffdf9;
|
||||
}
|
||||
|
||||
.atlas-readable .readable-badge {
|
||||
display: inline-flex;
|
||||
margin-top: 20px;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #f1c79f;
|
||||
border-radius: 6px;
|
||||
background: #fff4e8;
|
||||
color: #a85d25;
|
||||
font-size: 15px;
|
||||
font-weight: 750;
|
||||
}
|
||||
|
||||
.atlas-readable .flow {
|
||||
padding: 64px 72px 78px;
|
||||
}
|
||||
|
||||
.atlas-readable .flow-heading {
|
||||
min-height: 150px;
|
||||
margin-bottom: 36px;
|
||||
}
|
||||
|
||||
.atlas-readable .flow-heading > p {
|
||||
max-width: 510px;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.atlas-readable h2 {
|
||||
font-size: 38px;
|
||||
}
|
||||
|
||||
.atlas-readable .state-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 34px 30px;
|
||||
}
|
||||
|
||||
.atlas-readable .state-card {
|
||||
border-color: rgba(57, 40, 28, 0.14);
|
||||
box-shadow: 0 12px 30px rgba(70, 52, 37, 0.1);
|
||||
}
|
||||
|
||||
.atlas-readable .state-card img {
|
||||
image-rendering: auto;
|
||||
}
|
||||
|
||||
.atlas-readable .state-copy {
|
||||
grid-template-columns: 48px minmax(0, 1fr);
|
||||
gap: 16px;
|
||||
min-height: 132px;
|
||||
padding: 22px;
|
||||
}
|
||||
|
||||
.atlas-readable .state-copy > b {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.atlas-readable .state-copy h3 {
|
||||
margin-bottom: 8px;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.atlas-readable .state-copy p {
|
||||
font-size: 17px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.atlas-readable footer {
|
||||
min-height: 112px;
|
||||
font-size: 15px;
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>伴伴小程序当前页面全景 · 清晰阅读版</title>
|
||||
<link rel="stylesheet" href="overview-readable.css">
|
||||
</head>
|
||||
<body class="atlas-readable">
|
||||
<main class="atlas">
|
||||
<header class="atlas-header">
|
||||
<div class="header-copy">
|
||||
<div class="eyebrow">BANBAN MINI PROGRAM · CURRENT BUILD</div>
|
||||
<h1>伴伴小程序 / 当前页面全景</h1>
|
||||
<div class="readable-badge">高清阅读版 · 双列展示</div>
|
||||
<p>全部页面画面来自当前 <strong>banban-mini</strong> 构建,并由微信开发者工具直接截图。登录态使用本地固定展示数据,不包含线上用户数据。</p>
|
||||
<div class="legend">
|
||||
<span><i class="dot orange"></i>游客与入口</span>
|
||||
<span><i class="dot green"></i>设备与绑定</span>
|
||||
<span><i class="dot blue"></i>会话与定位</span>
|
||||
<span><i class="dot coral"></i>管理与家庭</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="metrics" aria-label="页面统计">
|
||||
<div class="metric"><strong>11</strong><span>当前路由</span></div>
|
||||
<div class="metric"><strong>26</strong><span>真实截图状态</span></div>
|
||||
<div class="metric"><strong>4</strong><span>核心 Tab</span></div>
|
||||
<div class="metric wide"><strong>390 px</strong><span>微信模拟器原始截图宽度</span></div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="flow flow-entry">
|
||||
<div class="flow-heading">
|
||||
<div>
|
||||
<span class="flow-number">01</span>
|
||||
<p class="flow-eyebrow">ENTRY & DEVICE</p>
|
||||
<h2>进入、登录与设备首页</h2>
|
||||
</div>
|
||||
<p>从游客浏览、登录入口到设备绑定后的状态、告警和设备详情。</p>
|
||||
</div>
|
||||
<div class="state-grid">
|
||||
<article class="state-card"><img src="actual-screens/01-device-guest.png" alt="设备首页游客态"><div class="state-copy"><b>01</b><div><h3>首页 · 游客态</h3><p>未登录时的设备入口</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/02-login.png" alt="登录页"><div class="state-copy"><b>02</b><div><h3>登录 · 默认</h3><p>微信登录与协议确认</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/08-device-online.png" alt="已绑定设备首页"><div class="state-copy"><b>03</b><div><h3>首页 · 已绑定</h3><p>电量、休眠、音量与告警</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/09-device-detail.png" alt="设备详情展开"><div class="state-copy"><b>04</b><div><h3>首页 · 详情展开</h3><p>告警位置与设备详细信息</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/06-notification-invalid.png" alt="通知链接失效"><div class="state-copy"><b>05</b><div><h3>通知 · 链接失效</h3><p>无效通知的回退页面</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/20-bind-scan.png" alt="设备扫码绑定入口"><div class="state-copy"><b>06</b><div><h3>绑定 · 扫码入口</h3><p>扫码或填写设备信息</p></div></div></article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="flow flow-binding">
|
||||
<div class="flow-heading">
|
||||
<div>
|
||||
<span class="flow-number">02</span>
|
||||
<p class="flow-eyebrow">BINDING & DEVICE CONTROL</p>
|
||||
<h2>设备二维码、贴卡与休眠</h2>
|
||||
</div>
|
||||
<p>当前绑定页真实展示二维码识别、等待贴卡、追加卡片和休眠配置。</p>
|
||||
</div>
|
||||
<div class="state-grid">
|
||||
<article class="state-card"><img src="actual-screens/21-bind-recognized.png" alt="设备二维码已识别"><div class="state-copy"><b>07</b><div><h3>绑定 · 已识别</h3><p>设备号和序列号已回填</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/22-bind-waiting-card.png" alt="等待设备贴卡"><div class="state-copy"><b>08</b><div><h3>绑定 · 等待贴卡</h3><p>绑卡指令和倒计时状态</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/23-bind-add-card.png" alt="添加设备卡片"><div class="state-copy"><b>09</b><div><h3>卡片 · 添加入口</h3><p>为当前设备追加新卡</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/24-sleep-schedule.png" alt="定时休眠设置"><div class="state-copy"><b>10</b><div><h3>设备 · 定时休眠</h3><p>休眠时段和时区配置</p></div></div></article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="flow flow-social">
|
||||
<div class="flow-heading">
|
||||
<div>
|
||||
<span class="flow-number">03</span>
|
||||
<p class="flow-eyebrow">MESSAGES & LOCATION</p>
|
||||
<h2>亲子会话与设备定位</h2>
|
||||
</div>
|
||||
<p>覆盖游客预览、会话列表、AI 与家长消息,以及绑定设备后的实时位置。</p>
|
||||
</div>
|
||||
<div class="state-grid">
|
||||
<article class="state-card"><img src="actual-screens/03-chat-guest.png" alt="玩伴游客态"><div class="state-copy"><b>11</b><div><h3>玩伴 · 游客态</h3><p>未登录时的会话能力预览</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/10-chat-list.png" alt="会话列表"><div class="state-copy"><b>12</b><div><h3>玩伴 · 会话列表</h3><p>AI、家长与宝贝会话</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/11-chat-ai-detail.png" alt="AI 会话详情"><div class="state-copy"><b>13</b><div><h3>会话 · AI 玩伴</h3><p>宝贝与伴伴的消息记录</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/12-chat-parent-detail.png" alt="家长会话详情"><div class="state-copy"><b>14</b><div><h3>会话 · 家长沟通</h3><p>文字、语音和留言控制</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/04-location-guest.png" alt="定位游客态"><div class="state-copy"><b>15</b><div><h3>定位 · 游客态</h3><p>地图与位置能力预览</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/13-location-current.png" alt="设备当前位置"><div class="state-copy"><b>16</b><div><h3>定位 · 当前位置</h3><p>地图、地址和定位参数</p></div></div></article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="flow flow-manage">
|
||||
<div class="flow-heading">
|
||||
<div>
|
||||
<span class="flow-number">04</span>
|
||||
<p class="flow-eyebrow">MANAGEMENT & FAMILY</p>
|
||||
<h2>管理、资料与家庭共享</h2>
|
||||
</div>
|
||||
<p>当前管理主页与真实交互弹窗,包含宝贝、卡片、角色、家庭和邀请状态。</p>
|
||||
</div>
|
||||
<div class="state-grid">
|
||||
<article class="state-card"><img src="actual-screens/05-manage-guest.png" alt="管理游客态"><div class="state-copy"><b>17</b><div><h3>管理 · 游客态</h3><p>管理功能的登录前预览</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/14-manage-overview.png" alt="管理总览"><div class="state-copy"><b>18</b><div><h3>管理 · 账号总览</h3><p>账号、设备和系统版本</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/15-manage-menu.png" alt="管理功能菜单"><div class="state-copy"><b>19</b><div><h3>管理 · 功能列表</h3><p>卡片、角色、家庭和通知</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/16-manage-child-modal.png" alt="切换当前宝贝弹窗"><div class="state-copy"><b>20</b><div><h3>宝贝 · 切换资料</h3><p>当前、修改和删除操作</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/17-manage-cards-modal.png" alt="我的卡片弹窗"><div class="state-copy"><b>21</b><div><h3>卡片 · 卡片列表</h3><p>卡片名称、UUID 和编辑</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/18-manage-role-modal.png" alt="切换 AI 角色弹窗"><div class="state-copy"><b>22</b><div><h3>角色 · AI 角色</h3><p>当前角色和可选角色</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/19-manage-family-modal.png" alt="家庭成员弹窗"><div class="state-copy"><b>23</b><div><h3>家庭 · 成员列表</h3><p>主成员、共享成员和邀请</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/25-family-invite-valid.png" alt="有效家庭邀请"><div class="state-copy"><b>24</b><div><h3>邀请 · 等待加入</h3><p>有效家庭共享邀请</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/26-family-invite-expired.png" alt="过期家庭邀请"><div class="state-copy"><b>25</b><div><h3>邀请 · 已失效</h3><p>过期邀请的禁用状态</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/07-family-invite-invalid.png" alt="未登录邀请跳转登录"><div class="state-copy"><b>26</b><div><h3>邀请 · 未登录跳转</h3><p>进入邀请前的登录承接页</p></div></div></article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<span>Generated from the current banban-mini build</span>
|
||||
<span>WeChat DevTools screenshots · local fixtures only · 2026-09-09</span>
|
||||
</footer>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,84 @@
|
||||
@import url("./overview.css");
|
||||
|
||||
.atlas-v2 {
|
||||
background: #eceae6;
|
||||
}
|
||||
|
||||
.atlas-v2 .atlas-header {
|
||||
min-height: 360px;
|
||||
padding: 58px 66px;
|
||||
border-bottom: 0;
|
||||
background: #302b27;
|
||||
color: #fffdf8;
|
||||
}
|
||||
|
||||
.atlas-v2 .eyebrow {
|
||||
color: #ffad69;
|
||||
}
|
||||
|
||||
.atlas-v2 .header-copy > p {
|
||||
color: rgba(255, 253, 248, 0.7);
|
||||
}
|
||||
|
||||
.atlas-v2 .legend {
|
||||
color: rgba(255, 253, 248, 0.78);
|
||||
}
|
||||
|
||||
.atlas-v2 .metric {
|
||||
border-color: rgba(255, 255, 255, 0.1);
|
||||
background: #413a34;
|
||||
}
|
||||
|
||||
.atlas-v2 .metric strong {
|
||||
color: #fffdf8;
|
||||
}
|
||||
|
||||
.atlas-v2 .metric span {
|
||||
color: rgba(255, 253, 248, 0.65);
|
||||
}
|
||||
|
||||
.atlas-v2 .flow {
|
||||
padding: 48px 50px 58px;
|
||||
}
|
||||
|
||||
.atlas-v2 .flow-entry { background: #fff8ed; }
|
||||
.atlas-v2 .flow-binding { background: #eef7e7; }
|
||||
.atlas-v2 .flow-social { background: #eaf5f8; }
|
||||
.atlas-v2 .flow-manage { background: #fff0ed; }
|
||||
|
||||
.atlas-v2 .flow-heading {
|
||||
min-height: 112px;
|
||||
margin-bottom: 24px;
|
||||
padding: 0 8px 20px;
|
||||
border-bottom: 2px solid rgba(57, 40, 28, 0.12);
|
||||
}
|
||||
|
||||
.atlas-v2 .flow-number {
|
||||
top: -22px;
|
||||
color: rgba(57, 40, 28, 0.08);
|
||||
font-size: 78px;
|
||||
}
|
||||
|
||||
.atlas-v2 .state-grid {
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.atlas-v2 .state-card {
|
||||
border-color: rgba(57, 40, 28, 0.1);
|
||||
box-shadow: 0 7px 18px rgba(57, 40, 28, 0.09);
|
||||
}
|
||||
|
||||
.atlas-v2 .state-copy {
|
||||
min-height: 92px;
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.atlas-v2 .state-copy > b {
|
||||
background: #ff8c42;
|
||||
color: #2d2118;
|
||||
}
|
||||
|
||||
.atlas-v2 footer {
|
||||
min-height: 92px;
|
||||
background: #1f1c19;
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>伴伴小程序当前页面全景 V2</title>
|
||||
<link rel="stylesheet" href="overview-v2.css">
|
||||
</head>
|
||||
<body class="atlas-v2">
|
||||
<main class="atlas">
|
||||
<header class="atlas-header">
|
||||
<div class="header-copy">
|
||||
<div class="eyebrow">BANBAN MINI PROGRAM · CURRENT BUILD</div>
|
||||
<h1>伴伴小程序 / 当前页面全景</h1>
|
||||
<p>全部页面画面来自当前 <strong>banban-mini</strong> 构建,并由微信开发者工具直接截图。登录态使用本地固定展示数据,不包含线上用户数据。</p>
|
||||
<div class="legend">
|
||||
<span><i class="dot orange"></i>游客与入口</span>
|
||||
<span><i class="dot green"></i>设备与绑定</span>
|
||||
<span><i class="dot blue"></i>会话与定位</span>
|
||||
<span><i class="dot coral"></i>管理与家庭</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="metrics" aria-label="页面统计">
|
||||
<div class="metric"><strong>11</strong><span>当前路由</span></div>
|
||||
<div class="metric"><strong>26</strong><span>真实截图状态</span></div>
|
||||
<div class="metric"><strong>4</strong><span>核心 Tab</span></div>
|
||||
<div class="metric wide"><strong>390 px</strong><span>微信模拟器原始截图宽度</span></div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="flow flow-entry">
|
||||
<div class="flow-heading">
|
||||
<div>
|
||||
<span class="flow-number">01</span>
|
||||
<p class="flow-eyebrow">ENTRY & DEVICE</p>
|
||||
<h2>进入、登录与设备首页</h2>
|
||||
</div>
|
||||
<p>从游客浏览、登录入口到设备绑定后的状态、告警和设备详情。</p>
|
||||
</div>
|
||||
<div class="state-grid">
|
||||
<article class="state-card"><img src="actual-screens/01-device-guest.png" alt="设备首页游客态"><div class="state-copy"><b>01</b><div><h3>首页 · 游客态</h3><p>未登录时的设备入口</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/02-login.png" alt="登录页"><div class="state-copy"><b>02</b><div><h3>登录 · 默认</h3><p>微信登录与协议确认</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/08-device-online.png" alt="已绑定设备首页"><div class="state-copy"><b>03</b><div><h3>首页 · 已绑定</h3><p>电量、休眠、音量与告警</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/09-device-detail.png" alt="设备详情展开"><div class="state-copy"><b>04</b><div><h3>首页 · 详情展开</h3><p>告警位置与设备详细信息</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/06-notification-invalid.png" alt="通知链接失效"><div class="state-copy"><b>05</b><div><h3>通知 · 链接失效</h3><p>无效通知的回退页面</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/20-bind-scan.png" alt="设备扫码绑定入口"><div class="state-copy"><b>06</b><div><h3>绑定 · 扫码入口</h3><p>扫码或填写设备信息</p></div></div></article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="flow flow-binding">
|
||||
<div class="flow-heading">
|
||||
<div>
|
||||
<span class="flow-number">02</span>
|
||||
<p class="flow-eyebrow">BINDING & DEVICE CONTROL</p>
|
||||
<h2>设备二维码、贴卡与休眠</h2>
|
||||
</div>
|
||||
<p>当前绑定页真实展示二维码识别、等待贴卡、追加卡片和休眠配置。</p>
|
||||
</div>
|
||||
<div class="state-grid">
|
||||
<article class="state-card"><img src="actual-screens/21-bind-recognized.png" alt="设备二维码已识别"><div class="state-copy"><b>07</b><div><h3>绑定 · 已识别</h3><p>设备号和序列号已回填</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/22-bind-waiting-card.png" alt="等待设备贴卡"><div class="state-copy"><b>08</b><div><h3>绑定 · 等待贴卡</h3><p>绑卡指令和倒计时状态</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/23-bind-add-card.png" alt="添加设备卡片"><div class="state-copy"><b>09</b><div><h3>卡片 · 添加入口</h3><p>为当前设备追加新卡</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/24-sleep-schedule.png" alt="定时休眠设置"><div class="state-copy"><b>10</b><div><h3>设备 · 定时休眠</h3><p>休眠时段和时区配置</p></div></div></article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="flow flow-social">
|
||||
<div class="flow-heading">
|
||||
<div>
|
||||
<span class="flow-number">03</span>
|
||||
<p class="flow-eyebrow">MESSAGES & LOCATION</p>
|
||||
<h2>亲子会话与设备定位</h2>
|
||||
</div>
|
||||
<p>覆盖游客预览、会话列表、AI 与家长消息,以及绑定设备后的实时位置。</p>
|
||||
</div>
|
||||
<div class="state-grid">
|
||||
<article class="state-card"><img src="actual-screens/03-chat-guest.png" alt="玩伴游客态"><div class="state-copy"><b>11</b><div><h3>玩伴 · 游客态</h3><p>未登录时的会话能力预览</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/10-chat-list.png" alt="会话列表"><div class="state-copy"><b>12</b><div><h3>玩伴 · 会话列表</h3><p>AI、家长与宝贝会话</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/11-chat-ai-detail.png" alt="AI 会话详情"><div class="state-copy"><b>13</b><div><h3>会话 · AI 玩伴</h3><p>宝贝与伴伴的消息记录</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/12-chat-parent-detail.png" alt="家长会话详情"><div class="state-copy"><b>14</b><div><h3>会话 · 家长沟通</h3><p>文字、语音和留言控制</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/04-location-guest.png" alt="定位游客态"><div class="state-copy"><b>15</b><div><h3>定位 · 游客态</h3><p>地图与位置能力预览</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/13-location-current.png" alt="设备当前位置"><div class="state-copy"><b>16</b><div><h3>定位 · 当前位置</h3><p>地图、地址和定位参数</p></div></div></article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="flow flow-manage">
|
||||
<div class="flow-heading">
|
||||
<div>
|
||||
<span class="flow-number">04</span>
|
||||
<p class="flow-eyebrow">MANAGEMENT & FAMILY</p>
|
||||
<h2>管理、资料与家庭共享</h2>
|
||||
</div>
|
||||
<p>当前管理主页与真实交互弹窗,包含宝贝、卡片、角色、家庭和邀请状态。</p>
|
||||
</div>
|
||||
<div class="state-grid">
|
||||
<article class="state-card"><img src="actual-screens/05-manage-guest.png" alt="管理游客态"><div class="state-copy"><b>17</b><div><h3>管理 · 游客态</h3><p>管理功能的登录前预览</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/14-manage-overview.png" alt="管理总览"><div class="state-copy"><b>18</b><div><h3>管理 · 账号总览</h3><p>账号、设备和系统版本</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/15-manage-menu.png" alt="管理功能菜单"><div class="state-copy"><b>19</b><div><h3>管理 · 功能列表</h3><p>卡片、角色、家庭和通知</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/16-manage-child-modal.png" alt="切换当前宝贝弹窗"><div class="state-copy"><b>20</b><div><h3>宝贝 · 切换资料</h3><p>当前、修改和删除操作</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/17-manage-cards-modal.png" alt="我的卡片弹窗"><div class="state-copy"><b>21</b><div><h3>卡片 · 卡片列表</h3><p>卡片名称、UUID 和编辑</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/18-manage-role-modal.png" alt="切换 AI 角色弹窗"><div class="state-copy"><b>22</b><div><h3>角色 · AI 角色</h3><p>当前角色和可选角色</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/19-manage-family-modal.png" alt="家庭成员弹窗"><div class="state-copy"><b>23</b><div><h3>家庭 · 成员列表</h3><p>主成员、共享成员和邀请</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/25-family-invite-valid.png" alt="有效家庭邀请"><div class="state-copy"><b>24</b><div><h3>邀请 · 等待加入</h3><p>有效家庭共享邀请</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/26-family-invite-expired.png" alt="过期家庭邀请"><div class="state-copy"><b>25</b><div><h3>邀请 · 已失效</h3><p>过期邀请的禁用状态</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/07-family-invite-invalid.png" alt="未登录邀请跳转登录"><div class="state-copy"><b>26</b><div><h3>邀请 · 未登录跳转</h3><p>进入邀请前的登录承接页</p></div></div></article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<span>Generated from the current banban-mini build</span>
|
||||
<span>WeChat DevTools screenshots · local fixtures only · 2026-09-09</span>
|
||||
</footer>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,242 @@
|
||||
:root {
|
||||
color-scheme: light;
|
||||
--ink: #39281c;
|
||||
--muted: #77685c;
|
||||
--line: rgba(73, 54, 38, 0.12);
|
||||
--orange: #ff8c42;
|
||||
--green: #b8ec20;
|
||||
--blue: #77bfea;
|
||||
--coral: #ef7f72;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
background: #f3f0ea;
|
||||
color: var(--ink);
|
||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
body { width: 1440px; }
|
||||
|
||||
.atlas {
|
||||
width: 1440px;
|
||||
overflow: hidden;
|
||||
background: #f8f6f1;
|
||||
}
|
||||
|
||||
.atlas-header {
|
||||
display: grid;
|
||||
min-height: 390px;
|
||||
grid-template-columns: minmax(0, 1fr) 440px;
|
||||
gap: 58px;
|
||||
align-items: center;
|
||||
padding: 64px 72px 58px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
background: #fffdf9;
|
||||
}
|
||||
|
||||
.eyebrow,
|
||||
.flow-eyebrow {
|
||||
margin: 0 0 12px;
|
||||
color: #a36a38;
|
||||
font-size: 15px;
|
||||
font-weight: 750;
|
||||
line-height: 1.4;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
h1 {
|
||||
max-width: 760px;
|
||||
margin: 0;
|
||||
font-size: 54px;
|
||||
font-weight: 820;
|
||||
line-height: 1.12;
|
||||
}
|
||||
|
||||
.header-copy > p {
|
||||
max-width: 790px;
|
||||
margin: 20px 0 0;
|
||||
color: var(--muted);
|
||||
font-size: 18px;
|
||||
line-height: 1.72;
|
||||
}
|
||||
|
||||
.legend {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px 24px;
|
||||
margin-top: 26px;
|
||||
color: #695b50;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.legend span {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.dot {
|
||||
display: inline-block;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.dot.orange { background: var(--orange); }
|
||||
.dot.green { background: #80b917; }
|
||||
.dot.blue { background: #4d9dce; }
|
||||
.dot.coral { background: var(--coral); }
|
||||
|
||||
.metrics {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.metric {
|
||||
min-height: 124px;
|
||||
padding: 22px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.metric.wide { grid-column: span 2; }
|
||||
|
||||
.metric strong {
|
||||
display: block;
|
||||
margin-bottom: 9px;
|
||||
color: #28211c;
|
||||
font-size: 40px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.metric span {
|
||||
color: var(--muted);
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.flow {
|
||||
padding: 54px 52px 66px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.flow-entry { background: #fff9f1; }
|
||||
.flow-binding { background: #f3f9ec; }
|
||||
.flow-social { background: #f0f7fa; }
|
||||
.flow-manage { background: #fff5f3; }
|
||||
|
||||
.flow-heading {
|
||||
display: flex;
|
||||
min-height: 130px;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
gap: 40px;
|
||||
margin: 0 6px 30px;
|
||||
}
|
||||
|
||||
.flow-heading > div { position: relative; }
|
||||
|
||||
.flow-number {
|
||||
position: absolute;
|
||||
top: -32px;
|
||||
left: 0;
|
||||
color: rgba(57, 40, 28, 0.09);
|
||||
font-size: 88px;
|
||||
font-weight: 850;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.flow-eyebrow { position: relative; }
|
||||
|
||||
h2 {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
font-size: 34px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.flow-heading > p {
|
||||
max-width: 440px;
|
||||
margin: 0 0 2px;
|
||||
color: var(--muted);
|
||||
font-size: 15px;
|
||||
line-height: 1.7;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.state-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 24px 20px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.state-card {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
box-shadow: 0 10px 24px rgba(70, 52, 37, 0.08);
|
||||
}
|
||||
|
||||
.state-card img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-bottom: 1px solid var(--line);
|
||||
background: #fff9ea;
|
||||
}
|
||||
|
||||
.state-copy {
|
||||
display: grid;
|
||||
grid-template-columns: 34px minmax(0, 1fr);
|
||||
gap: 12px;
|
||||
min-height: 100px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.state-copy > b {
|
||||
display: flex;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
background: #35291f;
|
||||
color: #fff;
|
||||
font-size: 11px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.state-copy h3 {
|
||||
margin: 0 0 6px;
|
||||
font-size: 17px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.state-copy p {
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
footer {
|
||||
display: flex;
|
||||
min-height: 100px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 30px;
|
||||
padding: 0 64px;
|
||||
background: #312820;
|
||||
color: rgba(255, 255, 255, 0.72);
|
||||
font-size: 13px;
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>伴伴小程序当前页面全景</title>
|
||||
<link rel="stylesheet" href="overview.css">
|
||||
</head>
|
||||
<body>
|
||||
<main class="atlas">
|
||||
<header class="atlas-header">
|
||||
<div class="header-copy">
|
||||
<div class="eyebrow">BANBAN MINI PROGRAM · CURRENT BUILD</div>
|
||||
<h1>伴伴小程序 / 当前页面全景</h1>
|
||||
<p>全部页面画面来自当前 <strong>banban-mini</strong> 构建,并由微信开发者工具直接截图。登录态使用本地固定展示数据,不包含线上用户数据。</p>
|
||||
<div class="legend">
|
||||
<span><i class="dot orange"></i>游客与入口</span>
|
||||
<span><i class="dot green"></i>设备与绑定</span>
|
||||
<span><i class="dot blue"></i>会话与定位</span>
|
||||
<span><i class="dot coral"></i>管理与家庭</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="metrics" aria-label="页面统计">
|
||||
<div class="metric"><strong>11</strong><span>当前路由</span></div>
|
||||
<div class="metric"><strong>26</strong><span>真实截图状态</span></div>
|
||||
<div class="metric"><strong>4</strong><span>核心 Tab</span></div>
|
||||
<div class="metric wide"><strong>390 px</strong><span>微信模拟器原始截图宽度</span></div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="flow flow-entry">
|
||||
<div class="flow-heading">
|
||||
<div>
|
||||
<span class="flow-number">01</span>
|
||||
<p class="flow-eyebrow">ENTRY & DEVICE</p>
|
||||
<h2>进入、登录与设备首页</h2>
|
||||
</div>
|
||||
<p>从游客浏览、登录入口到设备绑定后的状态、告警和设备详情。</p>
|
||||
</div>
|
||||
<div class="state-grid">
|
||||
<article class="state-card"><img src="actual-screens/01-device-guest.png" alt="设备首页游客态"><div class="state-copy"><b>01</b><div><h3>首页 · 游客态</h3><p>未登录时的设备入口</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/02-login.png" alt="登录页"><div class="state-copy"><b>02</b><div><h3>登录 · 默认</h3><p>微信登录与协议确认</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/08-device-online.png" alt="已绑定设备首页"><div class="state-copy"><b>03</b><div><h3>首页 · 已绑定</h3><p>电量、休眠、音量与告警</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/09-device-detail.png" alt="设备详情展开"><div class="state-copy"><b>04</b><div><h3>首页 · 详情展开</h3><p>告警位置与设备详细信息</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/06-notification-invalid.png" alt="通知链接失效"><div class="state-copy"><b>05</b><div><h3>通知 · 链接失效</h3><p>无效通知的回退页面</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/20-bind-scan.png" alt="设备扫码绑定入口"><div class="state-copy"><b>06</b><div><h3>绑定 · 扫码入口</h3><p>扫码或填写设备信息</p></div></div></article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="flow flow-binding">
|
||||
<div class="flow-heading">
|
||||
<div>
|
||||
<span class="flow-number">02</span>
|
||||
<p class="flow-eyebrow">BINDING & DEVICE CONTROL</p>
|
||||
<h2>设备二维码、贴卡与休眠</h2>
|
||||
</div>
|
||||
<p>当前绑定页真实展示二维码识别、等待贴卡、追加卡片和休眠配置。</p>
|
||||
</div>
|
||||
<div class="state-grid">
|
||||
<article class="state-card"><img src="actual-screens/21-bind-recognized.png" alt="设备二维码已识别"><div class="state-copy"><b>07</b><div><h3>绑定 · 已识别</h3><p>设备号和序列号已回填</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/22-bind-waiting-card.png" alt="等待设备贴卡"><div class="state-copy"><b>08</b><div><h3>绑定 · 等待贴卡</h3><p>绑卡指令和倒计时状态</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/23-bind-add-card.png" alt="添加设备卡片"><div class="state-copy"><b>09</b><div><h3>卡片 · 添加入口</h3><p>为当前设备追加新卡</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/24-sleep-schedule.png" alt="定时休眠设置"><div class="state-copy"><b>10</b><div><h3>设备 · 定时休眠</h3><p>休眠时段和时区配置</p></div></div></article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="flow flow-social">
|
||||
<div class="flow-heading">
|
||||
<div>
|
||||
<span class="flow-number">03</span>
|
||||
<p class="flow-eyebrow">MESSAGES & LOCATION</p>
|
||||
<h2>亲子会话与设备定位</h2>
|
||||
</div>
|
||||
<p>覆盖游客预览、会话列表、AI 与家长消息,以及绑定设备后的实时位置。</p>
|
||||
</div>
|
||||
<div class="state-grid">
|
||||
<article class="state-card"><img src="actual-screens/03-chat-guest.png" alt="玩伴游客态"><div class="state-copy"><b>11</b><div><h3>玩伴 · 游客态</h3><p>未登录时的会话能力预览</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/10-chat-list.png" alt="会话列表"><div class="state-copy"><b>12</b><div><h3>玩伴 · 会话列表</h3><p>AI、家长与宝贝会话</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/11-chat-ai-detail.png" alt="AI 会话详情"><div class="state-copy"><b>13</b><div><h3>会话 · AI 玩伴</h3><p>宝贝与伴伴的消息记录</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/12-chat-parent-detail.png" alt="家长会话详情"><div class="state-copy"><b>14</b><div><h3>会话 · 家长沟通</h3><p>文字、语音和留言控制</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/04-location-guest.png" alt="定位游客态"><div class="state-copy"><b>15</b><div><h3>定位 · 游客态</h3><p>地图与位置能力预览</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/13-location-current.png" alt="设备当前位置"><div class="state-copy"><b>16</b><div><h3>定位 · 当前位置</h3><p>地图、地址和定位参数</p></div></div></article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="flow flow-manage">
|
||||
<div class="flow-heading">
|
||||
<div>
|
||||
<span class="flow-number">04</span>
|
||||
<p class="flow-eyebrow">MANAGEMENT & FAMILY</p>
|
||||
<h2>管理、资料与家庭共享</h2>
|
||||
</div>
|
||||
<p>当前管理主页与真实交互弹窗,包含宝贝、卡片、角色、家庭和邀请状态。</p>
|
||||
</div>
|
||||
<div class="state-grid">
|
||||
<article class="state-card"><img src="actual-screens/05-manage-guest.png" alt="管理游客态"><div class="state-copy"><b>17</b><div><h3>管理 · 游客态</h3><p>管理功能的登录前预览</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/14-manage-overview.png" alt="管理总览"><div class="state-copy"><b>18</b><div><h3>管理 · 账号总览</h3><p>账号、设备和系统版本</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/15-manage-menu.png" alt="管理功能菜单"><div class="state-copy"><b>19</b><div><h3>管理 · 功能列表</h3><p>卡片、角色、家庭和通知</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/16-manage-child-modal.png" alt="切换当前宝贝弹窗"><div class="state-copy"><b>20</b><div><h3>宝贝 · 切换资料</h3><p>当前、修改和删除操作</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/17-manage-cards-modal.png" alt="我的卡片弹窗"><div class="state-copy"><b>21</b><div><h3>卡片 · 卡片列表</h3><p>卡片名称、UUID 和编辑</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/18-manage-role-modal.png" alt="切换 AI 角色弹窗"><div class="state-copy"><b>22</b><div><h3>角色 · AI 角色</h3><p>当前角色和可选角色</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/19-manage-family-modal.png" alt="家庭成员弹窗"><div class="state-copy"><b>23</b><div><h3>家庭 · 成员列表</h3><p>主成员、共享成员和邀请</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/25-family-invite-valid.png" alt="有效家庭邀请"><div class="state-copy"><b>24</b><div><h3>邀请 · 等待加入</h3><p>有效家庭共享邀请</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/26-family-invite-expired.png" alt="过期家庭邀请"><div class="state-copy"><b>25</b><div><h3>邀请 · 已失效</h3><p>过期邀请的禁用状态</p></div></div></article>
|
||||
<article class="state-card"><img src="actual-screens/07-family-invite-invalid.png" alt="未登录邀请跳转登录"><div class="state-copy"><b>26</b><div><h3>邀请 · 未登录跳转</h3><p>进入邀请前的登录承接页</p></div></div></article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<span>Generated from the current banban-mini build</span>
|
||||
<span>WeChat DevTools screenshots · local fixtures only · 2026-09-09</span>
|
||||
</footer>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,156 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <CoreImage/CoreImage.h>
|
||||
#import <WebKit/WebKit.h>
|
||||
|
||||
static CGFloat PageWidth = 1440.0;
|
||||
static CGFloat OutputScale = 1.0;
|
||||
static CGFloat SharpenIntensity = 0.0;
|
||||
|
||||
@interface PageRenderer : NSObject <WKNavigationDelegate>
|
||||
@property(nonatomic, strong) WKWebView *webView;
|
||||
@property(nonatomic, strong) NSURL *inputURL;
|
||||
@property(nonatomic, strong) NSURL *outputURL;
|
||||
@end
|
||||
|
||||
@implementation PageRenderer
|
||||
|
||||
- (instancetype)initWithInputURL:(NSURL *)inputURL outputURL:(NSURL *)outputURL {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_inputURL = inputURL;
|
||||
_outputURL = outputURL;
|
||||
_webView = [[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, PageWidth, 900)];
|
||||
_webView.navigationDelegate = self;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)start {
|
||||
NSURL *repositoryRoot = self.inputURL.URLByDeletingLastPathComponent
|
||||
.URLByDeletingLastPathComponent.URLByDeletingLastPathComponent;
|
||||
[self.webView loadFileURL:self.inputURL allowingReadAccessToURL:repositoryRoot];
|
||||
}
|
||||
|
||||
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
|
||||
NSString *heightScript = @"Math.max(document.body.scrollHeight, document.documentElement.scrollHeight)";
|
||||
[webView evaluateJavaScript:heightScript completionHandler:^(id result, NSError *error) {
|
||||
if (error || ![result isKindOfClass:NSNumber.class]) {
|
||||
[self fail:[NSString stringWithFormat:@"Could not read page height: %@", error.localizedDescription ?: @"unknown error"]];
|
||||
return;
|
||||
}
|
||||
|
||||
CGFloat pageHeight = [(NSNumber *)result doubleValue];
|
||||
[webView setFrameSize:NSMakeSize(PageWidth, pageHeight)];
|
||||
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.8 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
WKSnapshotConfiguration *configuration = [[WKSnapshotConfiguration alloc] init];
|
||||
configuration.rect = CGRectMake(0, 0, PageWidth, pageHeight);
|
||||
configuration.snapshotWidth = @(PageWidth * OutputScale);
|
||||
|
||||
[webView takeSnapshotWithConfiguration:configuration completionHandler:^(NSImage *image, NSError *snapshotError) {
|
||||
NSBitmapImageRep *bitmap = nil;
|
||||
if (image && SharpenIntensity > 0.0) {
|
||||
CIImage *inputImage = [[CIImage alloc] initWithData:image.TIFFRepresentation];
|
||||
CIFilter *sharpen = [CIFilter filterWithName:@"CIUnsharpMask"];
|
||||
[sharpen setValue:inputImage forKey:kCIInputImageKey];
|
||||
[sharpen setValue:@(1.0) forKey:kCIInputRadiusKey];
|
||||
[sharpen setValue:@(SharpenIntensity) forKey:kCIInputIntensityKey];
|
||||
|
||||
CIImage *outputImage = sharpen.outputImage;
|
||||
CIContext *context = [CIContext contextWithOptions:nil];
|
||||
CGImageRef outputCGImage = outputImage
|
||||
? [context createCGImage:outputImage fromRect:inputImage.extent]
|
||||
: nil;
|
||||
if (outputCGImage) {
|
||||
bitmap = [[NSBitmapImageRep alloc] initWithCGImage:outputCGImage];
|
||||
CGImageRelease(outputCGImage);
|
||||
}
|
||||
} else if (image) {
|
||||
bitmap = [[NSBitmapImageRep alloc] initWithData:image.TIFFRepresentation];
|
||||
}
|
||||
NSData *pngData = [bitmap representationUsingType:NSBitmapImageFileTypePNG properties:@{}];
|
||||
if (snapshotError || !pngData) {
|
||||
[self fail:[NSString stringWithFormat:@"Could not create snapshot: %@", snapshotError.localizedDescription ?: @"unknown error"]];
|
||||
return;
|
||||
}
|
||||
|
||||
NSError *writeError = nil;
|
||||
if (![pngData writeToURL:self.outputURL options:NSDataWritingAtomic error:&writeError]) {
|
||||
[self fail:[NSString stringWithFormat:@"Could not write PNG: %@", writeError.localizedDescription]];
|
||||
return;
|
||||
}
|
||||
|
||||
printf("Rendered %dx%d at %.2fx, sharpen %.2f -> %s\n",
|
||||
(int)PageWidth,
|
||||
(int)pageHeight,
|
||||
OutputScale,
|
||||
SharpenIntensity,
|
||||
self.outputURL.path.UTF8String);
|
||||
exit(0);
|
||||
}];
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error {
|
||||
[self fail:[NSString stringWithFormat:@"Navigation failed: %@", error.localizedDescription]];
|
||||
}
|
||||
|
||||
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error {
|
||||
[self fail:[NSString stringWithFormat:@"Page load failed: %@", error.localizedDescription]];
|
||||
}
|
||||
|
||||
- (void)fail:(NSString *)message {
|
||||
fprintf(stderr, "%s\n", message.UTF8String);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
int main(int argc, const char *argv[]) {
|
||||
@autoreleasepool {
|
||||
if (argc < 3 || argc > 6) {
|
||||
fprintf(stderr, "Usage: render-overview <input.html> <output.png> [page-width] [output-scale] [sharpen-intensity]\n");
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (argc >= 4) {
|
||||
double requestedWidth = strtod(argv[3], NULL);
|
||||
if (requestedWidth < 320.0 || requestedWidth > 4096.0) {
|
||||
fprintf(stderr, "Page width must be between 320 and 4096 pixels\n");
|
||||
return 2;
|
||||
}
|
||||
PageWidth = (CGFloat)requestedWidth;
|
||||
}
|
||||
|
||||
if (argc >= 5) {
|
||||
double requestedScale = strtod(argv[4], NULL);
|
||||
if (requestedScale < 1.0 || requestedScale > 4.0) {
|
||||
fprintf(stderr, "Output scale must be between 1 and 4\n");
|
||||
return 2;
|
||||
}
|
||||
OutputScale = (CGFloat)requestedScale;
|
||||
}
|
||||
|
||||
if (argc == 6) {
|
||||
double requestedSharpness = strtod(argv[5], NULL);
|
||||
if (requestedSharpness < 0.0 || requestedSharpness > 1.0) {
|
||||
fprintf(stderr, "Sharpen intensity must be between 0 and 1\n");
|
||||
return 2;
|
||||
}
|
||||
SharpenIntensity = (CGFloat)requestedSharpness;
|
||||
}
|
||||
|
||||
[NSApplication sharedApplication];
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyProhibited];
|
||||
|
||||
NSString *inputPath = [NSString stringWithUTF8String:argv[1]].stringByStandardizingPath;
|
||||
NSString *outputPath = [NSString stringWithUTF8String:argv[2]].stringByStandardizingPath;
|
||||
PageRenderer *renderer = [[PageRenderer alloc]
|
||||
initWithInputURL:[NSURL fileURLWithPath:inputPath]
|
||||
outputURL:[NSURL fileURLWithPath:outputPath]];
|
||||
[renderer start];
|
||||
[NSRunLoop.mainRunLoop run];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
:root {
|
||||
--orange: #ff8c42;
|
||||
--orange-deep: #ff6b35;
|
||||
--orange-soft: #fef3e8;
|
||||
--ink: #1a1a1a;
|
||||
--muted: #666666;
|
||||
--quiet: #999999;
|
||||
--line: #ece9e5;
|
||||
--surface: #ffffff;
|
||||
--canvas: #f5f7fa;
|
||||
--green: #16a34a;
|
||||
--green-soft: #e8f8f0;
|
||||
--blue: #5b8ff9;
|
||||
--blue-soft: #e8f0fe;
|
||||
--red: #be123c;
|
||||
--red-soft: #fff1f2;
|
||||
--purple: #8b5cf6;
|
||||
--purple-soft: #f0e6ff;
|
||||
--yellow: #f59e0b;
|
||||
--yellow-soft: #fff7e8;
|
||||
--shadow: 0 10px 30px rgba(31, 41, 55, 0.08);
|
||||
--shadow-soft: 0 4px 16px rgba(31, 41, 55, 0.06);
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: #eceff3;
|
||||
color: var(--ink);
|
||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
button,
|
||||
input {
|
||||
font: inherit;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.poster {
|
||||
width: 1440px;
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
background: #fbfaf8;
|
||||
}
|
||||
|
||||
.poster-kicker {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
color: var(--orange-deep);
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.poster-kicker::before {
|
||||
width: 24px;
|
||||
height: 4px;
|
||||
border-radius: 999px;
|
||||
background: var(--orange);
|
||||
content: "";
|
||||
}
|
||||
|
||||
.poster-title {
|
||||
margin: 14px 0 8px;
|
||||
font-size: 56px;
|
||||
line-height: 1.12;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.poster-subtitle {
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
font-size: 22px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.meta-chip {
|
||||
display: inline-flex;
|
||||
min-height: 48px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 18px;
|
||||
border: 1px solid #ebe7e1;
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 255, 255, 0.86);
|
||||
color: #4b5563;
|
||||
font-size: 16px;
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
.section-eyebrow {
|
||||
color: var(--orange-deep);
|
||||
font-size: 15px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
margin: 8px 0 4px;
|
||||
font-size: 30px;
|
||||
line-height: 1.25;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.section-note {
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
font-size: 16px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.poster-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 34px 56px 40px;
|
||||
border-top: 1px solid #ece7df;
|
||||
color: #7b7b7b;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.footer-brand {
|
||||
color: var(--ink);
|
||||
font-weight: 800;
|
||||
}
|
||||
Reference in New Issue
Block a user