Files
qiming/qimingclaw/crates/agent-electron-client/public/sgrobot-digital/report/roadmap.html

627 lines
34 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>sgRobot 五月底上线任务分工表</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
.theme-dark-blue { background-color: #0d2758; }
.theme-text-dark-blue { color: #0d2758; }
.theme-light-blue-bg { background-color: #f0f6fc; }
.theme-border-blue { border-color: #aec3db; }
.theme-red { color: #d92d20; }
body {
font-family: "PingFang SC", "Microsoft YaHei", "Noto Sans SC", "Source Han Sans SC", Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
font-size: 18px;
}
.text-sm { font-size: 1.08rem !important; }
.text-base { font-size: 1.24rem !important; }
.text-lg { font-size: 1.4rem !important; }
.text-xl { font-size: 1.62rem !important; }
.text-3xl { font-size: 2.75rem !important; }
.text-\[13px\] { font-size: 1.14rem !important; }
[data-tip] { cursor: help; }
.panel {
border: 1px solid #aec3db;
border-radius: 8px;
background: white;
box-shadow: 0 8px 20px rgba(13, 39, 88, 0.08);
overflow: hidden;
}
.panel-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 14px 16px 10px 16px;
border-bottom: 1px solid #d8e4f2;
}
.panel-title {
display: flex;
align-items: center;
gap: 10px;
color: #0d2758;
font-weight: 900;
font-size: 1.3rem;
line-height: 1.25;
}
.panel-tag {
border: 1px solid #c8d7e8;
border-radius: 999px;
padding: 3px 10px;
color: #5f7082;
background: #f8fbff;
font-weight: 800;
white-space: nowrap;
font-size: 0.92rem;
}
.summary-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 12px;
padding: 14px;
background: #f0f6fc;
}
.summary-card {
border: 1px solid #aec3db;
border-radius: 8px;
background: white;
padding: 12px;
color: #0d2758;
}
.summary-card strong {
display: block;
font-size: 1.5rem;
line-height: 1;
margin-bottom: 6px;
}
.summary-card span {
display: block;
color: #5f7082;
font-weight: 800;
line-height: 1.32;
}
.task-table-wrap {
overflow-x: auto;
background: #f0f6fc;
}
.task-table {
width: 100%;
min-width: 1320px;
border-collapse: collapse;
color: #0d2758;
}
.task-table th {
background: #0d2758;
color: white;
text-align: left;
padding: 11px 12px;
font-weight: 900;
border-right: 1px solid #375078;
white-space: nowrap;
}
.task-table td {
background: white;
padding: 11px 12px;
border-right: 1px solid #d8e4f2;
border-bottom: 1px solid #d8e4f2;
vertical-align: top;
font-weight: 760;
line-height: 1.38;
}
.task-table tr:nth-child(even) td {
background: #f8fbff;
}
.task-name {
font-weight: 900;
color: #0d2758;
}
.date-pill {
display: inline-flex;
align-items: center;
border-radius: 999px;
background: #0d2758;
color: white;
padding: 4px 10px;
font-weight: 900;
white-space: nowrap;
}
.owner-pill {
display: inline-flex;
align-items: center;
border-radius: 999px;
border: 1px solid #c8d7e8;
background: white;
color: #0d2758;
padding: 4px 10px;
font-weight: 900;
white-space: nowrap;
}
.boundary-ok {
color: #0d2758;
}
.boundary-no {
color: #9f1239;
}
.interface-map-wrap {
overflow-x: auto;
background: #f0f6fc;
padding: 14px;
}
.interface-map {
min-width: 1320px;
width: 100%;
height: auto;
display: block;
border: 1px solid #d8e4f2;
border-radius: 8px;
background: white;
}
.map-note {
padding: 10px 14px 14px 14px;
background: #f0f6fc;
color: #5f7082;
font-weight: 800;
line-height: 1.4;
}
.timeline {
display: grid;
grid-template-columns: repeat(6, minmax(160px, 1fr));
gap: 10px;
padding: 14px;
background: #f0f6fc;
overflow-x: auto;
}
.time-card {
min-width: 170px;
border: 1px solid #aec3db;
border-radius: 8px;
background: white;
padding: 12px;
color: #0d2758;
}
.time-date {
display: inline-flex;
background: #0d2758;
color: white;
border-radius: 999px;
padding: 4px 10px;
font-weight: 900;
margin-bottom: 8px;
}
.time-title {
font-weight: 900;
line-height: 1.25;
margin-bottom: 5px;
}
.time-text {
color: #5f7082;
font-weight: 760;
line-height: 1.35;
}
#sg-tooltip {
position: fixed;
z-index: 9999;
max-width: 460px;
padding: 10px 12px;
border-radius: 6px;
background: #0d2758;
color: white;
font-size: 0.95rem;
line-height: 1.45;
box-shadow: 0 12px 32px rgba(13, 39, 88, 0.22);
pointer-events: none;
opacity: 0;
transform: translateY(4px);
transition: opacity 0.12s ease, transform 0.12s ease;
}
#sg-tooltip.is-visible {
opacity: 1;
transform: translateY(0);
}
@media (max-width: 1100px) {
.summary-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.timeline { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 720px) {
.summary-grid { grid-template-columns: 1fr; }
.timeline { grid-template-columns: 1fr; }
.panel-head { align-items: flex-start; flex-direction: column; }
}
</style>
</head>
<body class="bg-white p-4 md:p-8 min-h-screen text-sm select-none">
<div class="max-w-[1780px] mx-auto mb-4 border border-amber-300 bg-amber-50 text-amber-900 rounded-lg px-4 py-3 shadow-sm">
<div class="font-black text-base">历史汇报版</div>
<div class="text-sm leading-relaxed mt-1">
本页面保留为早期 10 任务包汇报视图,不再作为团队分工和接口冻结源头。当前分工、接口 owner、状态、Mock 和验收证据以
<code>plan/2026-04-26-architecture-team-handoff/</code>
及其中的 <code>interfaces/interface-freeze-register.md</code> 为准。
</div>
</div>
<nav class="max-w-[1780px] mx-auto mb-5 flex flex-wrap items-center justify-between gap-3 border border-blue-200 rounded-lg px-4 py-3 shadow-sm bg-white">
<div class="flex items-center gap-2 theme-text-dark-blue font-bold text-base" data-tip="汇报专用静态网站,可直接双击 index.html 打开,不需要启动本地服务。">
<i class="fa-solid fa-display"></i>
<span>sgRobot 汇报静态网站</span>
</div>
<div class="flex items-center gap-2">
<a href="index.html" class="border theme-border-blue theme-text-dark-blue rounded px-4 py-2 font-bold bg-white hover:bg-blue-50" data-tip="返回业务架构总览。">业务架构总览</a>
<a href="flows.html" class="border theme-border-blue theme-text-dark-blue rounded px-4 py-2 font-bold bg-white hover:bg-blue-50" data-tip="查看业务流程如何落到架构模块。">业务流程图</a>
<a href="roadmap.html" class="theme-dark-blue text-white rounded px-4 py-2 font-bold shadow-sm" data-tip="当前页面只看任务、负责人、日期和边界。">里程碑与分工</a>
</div>
</nav>
<header class="max-w-[1780px] mx-auto mb-6">
<div class="flex items-center mb-2" data-tip="这一页只保留领导最需要看的信息:哪个任务、谁负责、什么时候完成、边界是什么。">
<div class="w-10 h-10 theme-dark-blue text-white flex items-center justify-center rounded shadow-sm mr-3">
<i class="fa-solid fa-list-check text-xl"></i>
</div>
<div>
<h1 class="text-3xl font-bold theme-text-dark-blue tracking-wide">sgRobot <span class="font-bold">五月底上线任务分工表</span></h1>
</div>
</div>
<div class="ml-14 text-[13px] font-medium text-gray-700 leading-relaxed" data-tip="负责人先按角色写,后续可以替换成具体姓名。">
原则:<span class="font-bold">一个任务,一个负责人,一个完成日期,一个边界</span>。各模块只按公共接口交付不互相改代码5 月 31 日统一验收。
</div>
</header>
<main class="max-w-[1780px] mx-auto flex flex-col gap-5">
<section class="panel" data-tip="这四个数字是领导快速判断范围用的。">
<div class="panel-head">
<div class="panel-title"><i class="fa-solid fa-circle-info"></i><span>一页结论</span></div>
<span class="panel-tag" data-tip="如需具体人名,直接把负责人角色替换为姓名即可。">负责人先按角色占位</span>
</div>
<div class="summary-grid">
<div class="summary-card" data-tip="共 10 个任务包;公共接口只做口径冻结,核心控制另行实现,避免一个负责人背所有责任。"><strong>10 个</strong><span>任务包</span></div>
<div class="summary-card" data-tip="只设一个最终日期2026 年 5 月 31 日完成上线验收。"><strong>5/31</strong><span>上线验收收口</span></div>
<div class="summary-card" data-tip="模块之间只通过公共接口、测试样例和交付物对接。"><strong>互不改代码</strong><span>只按接口交付</span></div>
<div class="summary-card" data-tip="测试和发布有否决权,没证据就不能算完成。"><strong>有证据</strong><span>才算完成</span></div>
</div>
</section>
<section class="panel" data-tip="这是核心表。领导看这一张表就能知道谁负责什么、哪天完成、边界在哪里。">
<div class="panel-head">
<div class="panel-title"><i class="fa-solid fa-table"></i><span>任务分工总表</span></div>
<span class="panel-tag" data-tip="完成日期是建议倒排,可根据真实人员情况微调。">按完成日期倒排</span>
</div>
<div class="task-table-wrap">
<table class="task-table">
<thead>
<tr>
<th>任务</th>
<th>负责人</th>
<th>完成日期</th>
<th>交付物</th>
<th>边界:只负责</th>
<th>边界:不负责</th>
</tr>
</thead>
<tbody>
<tr data-tip="公共接口不要求 4 月 30 日全部实现只要求各负责人把字段、调用方向、Mock 数据和验收口径确认下来。">
<td><span class="task-name">公共接口口径冻结</span></td>
<td><span class="owner-pill">总负责人 + 各模块负责人</span></td>
<td><span class="date-pill">4/30</span></td>
<td>接口目录、字段草案、Mock 数据、对接方向、验收口径</td>
<td class="boundary-ok">只冻结模块之间怎么对接,以及每个模块交付什么数据</td>
<td class="boundary-no">不要求一次性完成所有后端实现;不替各模块写内部代码</td>
</tr>
<tr data-tip="核心后端只负责把主链路跑起来,不再同时承担所有适配模块的接口设计责任。">
<td><span class="task-name">核心控制主链路</span></td>
<td><span class="owner-pill">核心后端负责人</span></td>
<td><span class="date-pill">5/08</span></td>
<td>计划、任务、执行记录、审批、服务占用、能力目录最小实现</td>
<td class="boundary-ok">核心状态推进、公共接口实现、主链路冒烟</td>
<td class="boundary-no">不写 UI不写浏览器、硬件、本地服务实现</td>
</tr>
<tr data-tip="解决新增 Skill 时自动写控制脚本的问题。">
<td><span class="task-name">Skill 编写助手</span></td>
<td><span class="owner-pill">Skill 工具负责人</span></td>
<td><span class="date-pill">5/08</span></td>
<td>控制脚本生成器、模板、校验器、3 个示例 Skill</td>
<td class="boundary-ok">自动生成新 Skill 的控制脚本和校验规则</td>
<td class="boundary-no">不做浏览器、硬件、本地服务底层适配</td>
</tr>
<tr data-tip="把已有自动化脚本批量纳入 Skill 体系。">
<td><span class="task-name">自动化脚本批量转 Skill</span></td>
<td><span class="owner-pill">脚本迁移负责人</span></td>
<td><span class="date-pill">5/12</span></td>
<td>转换工具、迁移清单、已转换 Skill 包、回归报告</td>
<td class="boundary-ok">旧脚本包装、补输入输出、补日志、登记能力目录</td>
<td class="boundary-no">不重写业务逻辑;不改适配器底层能力</td>
</tr>
<tr data-tip="浏览器适配独立成包,不和 UI 混在一起。">
<td><span class="task-name">浏览器适配</span></td>
<td><span class="owner-pill">浏览器适配负责人</span></td>
<td><span class="date-pill">5/12</span></td>
<td>浏览器适配器、示例 Skill、截图证据、失败说明</td>
<td class="boundary-ok">打开页面、点击、输入、下载、截图、会话和错误回写</td>
<td class="boundary-no">不决定业务审批;不做 UI 页面;不写硬件接口</td>
</tr>
<tr data-tip="子程序和本地服务单独管理,避免主程序和后台服务绑死。">
<td><span class="task-name">本地服务与子程序管理</span></td>
<td><span class="owner-pill">本地服务负责人</span></td>
<td><span class="date-pill">5/14</span></td>
<td>服务管理器、服务清单、健康数据、运维命令</td>
<td class="boundary-ok">邮件读取、监听器、后台进程启动停止、健康、日志、回收</td>
<td class="boundary-no">不定义业务流程;不直接修改任务结果</td>
</tr>
<tr data-tip="硬件适配要有安全边界,不能绕过人工治理。">
<td><span class="task-name">硬件与机器人适配</span></td>
<td><span class="owner-pill">硬件适配负责人</span></td>
<td><span class="date-pill">5/14</span></td>
<td>硬件适配器、模拟测试、状态回写、安全风险清单</td>
<td class="boundary-ok">设备连接、状态读取、基础动作、机器人控制、异常停止</td>
<td class="boundary-no">不绕过审批执行高风险动作;不负责浏览器自动化</td>
</tr>
<tr data-tip="UI 只做操作台,不能自己发明另一套后端状态。">
<td><span class="task-name">UI 操作台</span></td>
<td><span class="owner-pill">前端负责人</span></td>
<td><span class="date-pill">5/18</span></td>
<td>任务看板、审批队列、服务健康、成果浏览、过程回放</td>
<td class="boundary-ok">用户操作入口和可视化验收页面</td>
<td class="boundary-no">不绕过后端改状态;不自建任务模型</td>
</tr>
<tr data-tip="编译打包独立负责 Windows、信创、ARM不和业务开发绑在一起。">
<td><span class="task-name">浏览器编译与平台打包</span></td>
<td><span class="owner-pill">构建发布负责人</span></td>
<td><span class="date-pill">5/22</span></td>
<td>Windows / 信创 / ARM 构建产物、依赖清单、安装说明</td>
<td class="boundary-ok">平台构建、依赖整理、打包、失败归因</td>
<td class="boundary-no">不改业务逻辑;不写 Skill不做 UI 功能</td>
</tr>
<tr data-tip="测试发布负责最终收口和上线判断。">
<td><span class="task-name">测试验收与发布收口</span></td>
<td><span class="owner-pill">测试发布负责人</span></td>
<td><span class="date-pill">5/31</span></td>
<td>测试报告、上线风险、已知问题、发布包、回滚方案</td>
<td class="boundary-ok">模块测试、端到端测试、兼容验证、发布门禁</td>
<td class="boundary-no">不替功能模块补实现,但可判定是否阻断上线</td>
</tr>
</tbody>
</table>
</div>
</section>
<section class="panel" data-tip="这张图只表达接口关系:每个负责人都通过中间的公共接口对接,模块之间不互相改代码。">
<div class="panel-head">
<div class="panel-title"><i class="fa-solid fa-diagram-project"></i><span>分工接口关系图</span></div>
<span class="panel-tag" data-tip="线上的文字就是这个负责人和核心接口之间的交付接口。">看线上的接口名</span>
</div>
<div class="interface-map-wrap">
<svg class="interface-map" viewBox="0 0 1500 720" role="img" aria-label="sgRobot 五月底上线分工接口关系图">
<defs>
<marker id="arrow" markerWidth="10" markerHeight="10" refX="8" refY="3" orient="auto" markerUnits="strokeWidth">
<path d="M0,0 L0,6 L9,3 z" fill="#6f8faf"></path>
</marker>
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="0" dy="4" stdDeviation="4" flood-color="#0d2758" flood-opacity="0.12"></feDropShadow>
</filter>
</defs>
<rect x="40" y="30" width="1420" height="660" rx="10" fill="#f8fbff" stroke="#d8e4f2"></rect>
<rect x="555" y="64" width="390" height="92" rx="10" fill="#0d2758" filter="url(#shadow)"></rect>
<text x="750" y="102" text-anchor="middle" fill="#ffffff" font-size="25" font-weight="900">核心控制主链路</text>
<text x="750" y="132" text-anchor="middle" fill="#dbeafe" font-size="18" font-weight="800">核心后端负责人5/08 实现</text>
<rect x="565" y="285" width="370" height="118" rx="10" fill="#ffffff" stroke="#aec3db" stroke-width="2" filter="url(#shadow)"></rect>
<text x="750" y="325" text-anchor="middle" fill="#0d2758" font-size="24" font-weight="900">公共接口合同</text>
<text x="750" y="356" text-anchor="middle" fill="#5f7082" font-size="17" font-weight="800">总负责人 + 各模块负责人共同确认4/30</text>
<text x="750" y="383" text-anchor="middle" fill="#d92d20" font-size="17" font-weight="900">核心后端只实现主链路,不替各模块兜底</text>
<line x1="750" y1="156" x2="750" y2="285" stroke="#6f8faf" stroke-width="3" marker-end="url(#arrow)"></line>
<text x="766" y="226" fill="#5f7082" font-size="16" font-weight="800">核心接口实现</text>
<rect x="75" y="75" width="290" height="72" rx="8" fill="#ffffff" stroke="#aec3db" stroke-width="2" filter="url(#shadow)"></rect>
<text x="220" y="106" text-anchor="middle" fill="#0d2758" font-size="20" font-weight="900">Skill 编写助手</text>
<text x="220" y="131" text-anchor="middle" fill="#5f7082" font-size="16" font-weight="800">Skill 工具负责人</text>
<line x1="365" y1="111" x2="565" y2="302" stroke="#6f8faf" stroke-width="3" marker-end="url(#arrow)"></line>
<rect x="376" y="178" width="172" height="32" rx="16" fill="#ffffff" stroke="#c8d7e8"></rect>
<text x="462" y="200" text-anchor="middle" fill="#0d2758" font-size="15" font-weight="900">Skill 生成合同</text>
<rect x="75" y="185" width="290" height="72" rx="8" fill="#ffffff" stroke="#aec3db" stroke-width="2" filter="url(#shadow)"></rect>
<text x="220" y="216" text-anchor="middle" fill="#0d2758" font-size="20" font-weight="900">脚本批量转 Skill</text>
<text x="220" y="241" text-anchor="middle" fill="#5f7082" font-size="16" font-weight="800">脚本迁移负责人</text>
<line x1="365" y1="221" x2="565" y2="327" stroke="#6f8faf" stroke-width="3" marker-end="url(#arrow)"></line>
<rect x="386" y="253" width="160" height="32" rx="16" fill="#ffffff" stroke="#c8d7e8"></rect>
<text x="466" y="275" text-anchor="middle" fill="#0d2758" font-size="15" font-weight="900">Skill 包接口</text>
<rect x="75" y="295" width="290" height="72" rx="8" fill="#ffffff" stroke="#aec3db" stroke-width="2" filter="url(#shadow)"></rect>
<text x="220" y="326" text-anchor="middle" fill="#0d2758" font-size="20" font-weight="900">浏览器适配</text>
<text x="220" y="351" text-anchor="middle" fill="#5f7082" font-size="16" font-weight="800">浏览器适配负责人</text>
<line x1="365" y1="331" x2="565" y2="344" stroke="#6f8faf" stroke-width="3" marker-end="url(#arrow)"></line>
<rect x="384" y="333" width="158" height="32" rx="16" fill="#ffffff" stroke="#c8d7e8"></rect>
<text x="463" y="355" text-anchor="middle" fill="#0d2758" font-size="15" font-weight="900">浏览器接口</text>
<rect x="75" y="405" width="290" height="72" rx="8" fill="#ffffff" stroke="#aec3db" stroke-width="2" filter="url(#shadow)"></rect>
<text x="220" y="436" text-anchor="middle" fill="#0d2758" font-size="20" font-weight="900">本地服务管理</text>
<text x="220" y="461" text-anchor="middle" fill="#5f7082" font-size="16" font-weight="800">本地服务负责人</text>
<line x1="365" y1="441" x2="565" y2="366" stroke="#6f8faf" stroke-width="3" marker-end="url(#arrow)"></line>
<rect x="392" y="397" width="146" height="32" rx="16" fill="#ffffff" stroke="#c8d7e8"></rect>
<text x="465" y="419" text-anchor="middle" fill="#0d2758" font-size="15" font-weight="900">服务合同</text>
<rect x="75" y="515" width="290" height="72" rx="8" fill="#ffffff" stroke="#aec3db" stroke-width="2" filter="url(#shadow)"></rect>
<text x="220" y="546" text-anchor="middle" fill="#0d2758" font-size="20" font-weight="900">硬件与机器人适配</text>
<text x="220" y="571" text-anchor="middle" fill="#5f7082" font-size="16" font-weight="800">硬件适配负责人</text>
<line x1="365" y1="551" x2="565" y2="388" stroke="#6f8faf" stroke-width="3" marker-end="url(#arrow)"></line>
<rect x="382" y="474" width="168" height="32" rx="16" fill="#ffffff" stroke="#c8d7e8"></rect>
<text x="466" y="496" text-anchor="middle" fill="#0d2758" font-size="15" font-weight="900">硬件适配接口</text>
<rect x="1135" y="120" width="290" height="72" rx="8" fill="#ffffff" stroke="#aec3db" stroke-width="2" filter="url(#shadow)"></rect>
<text x="1280" y="151" text-anchor="middle" fill="#0d2758" font-size="20" font-weight="900">UI 操作台</text>
<text x="1280" y="176" text-anchor="middle" fill="#5f7082" font-size="16" font-weight="800">前端负责人</text>
<line x1="1135" y1="156" x2="935" y2="310" stroke="#6f8faf" stroke-width="3" marker-end="url(#arrow)"></line>
<rect x="960" y="220" width="152" height="32" rx="16" fill="#ffffff" stroke="#c8d7e8"></rect>
<text x="1036" y="242" text-anchor="middle" fill="#0d2758" font-size="15" font-weight="900">UI 数据接口</text>
<rect x="1135" y="285" width="290" height="72" rx="8" fill="#ffffff" stroke="#aec3db" stroke-width="2" filter="url(#shadow)"></rect>
<text x="1280" y="316" text-anchor="middle" fill="#0d2758" font-size="20" font-weight="900">浏览器编译与打包</text>
<text x="1280" y="341" text-anchor="middle" fill="#5f7082" font-size="16" font-weight="800">构建发布负责人</text>
<line x1="1135" y1="321" x2="935" y2="344" stroke="#6f8faf" stroke-width="3" marker-end="url(#arrow)"></line>
<rect x="954" y="311" width="170" height="32" rx="16" fill="#ffffff" stroke="#c8d7e8"></rect>
<text x="1039" y="333" text-anchor="middle" fill="#0d2758" font-size="15" font-weight="900">构建产物接口</text>
<rect x="1135" y="450" width="290" height="72" rx="8" fill="#ffffff" stroke="#aec3db" stroke-width="2" filter="url(#shadow)"></rect>
<text x="1280" y="481" text-anchor="middle" fill="#0d2758" font-size="20" font-weight="900">测试验收与发布</text>
<text x="1280" y="506" text-anchor="middle" fill="#5f7082" font-size="16" font-weight="800">测试发布负责人</text>
<line x1="1135" y1="486" x2="935" y2="378" stroke="#6f8faf" stroke-width="3" marker-end="url(#arrow)"></line>
<rect x="958" y="412" width="158" height="32" rx="16" fill="#ffffff" stroke="#c8d7e8"></rect>
<text x="1037" y="434" text-anchor="middle" fill="#0d2758" font-size="15" font-weight="900">验收门禁</text>
<rect x="520" y="560" width="460" height="76" rx="10" fill="#fff7f7" stroke="#fecaca" stroke-width="2"></rect>
<text x="750" y="592" text-anchor="middle" fill="#9f1239" font-size="21" font-weight="900">规则:负责人之间不直接改对方模块</text>
<text x="750" y="620" text-anchor="middle" fill="#9f1239" font-size="17" font-weight="800">所有协作都落到接口合同、交付包、测试报告</text>
</svg>
</div>
<div class="map-note" data-tip="这句话是给领导看的解释口径。">
读图方式:每个白框是一个负责人,线上的文字是他交给核心控制或从核心控制读取的接口。除测试发布外,其他负责人不直接验收别人内部实现,只按接口和交付物对接。
</div>
</section>
<section class="panel" data-tip="把里程碑压缩到几个关键日期,领导只需要知道每个日期要看到什么。">
<div class="panel-head">
<div class="panel-title"><i class="fa-solid fa-calendar-days"></i><span>简版里程碑</span></div>
<span class="panel-tag" data-tip="不再画复杂甘特图,只保留关键日期。">哪个任务哪天完成</span>
</div>
<div class="timeline">
<div class="time-card" data-tip="这里只冻结口径,不要求完成全部实现。目标是让各模块能并行开工。">
<span class="time-date">4/30</span>
<div class="time-title">公共接口口径冻结</div>
<div class="time-text">接口目录、字段草案、Mock 数据、对接方向确认。</div>
</div>
<div class="time-card" data-tip="核心后端在这个日期前交付主链路最小实现,不再背所有适配模块的实现。">
<span class="time-date">5/08</span>
<div class="time-title">核心主链路完成</div>
<div class="time-text">计划、任务、执行记录、审批、服务占用可冒烟。</div>
</div>
<div class="time-card" data-tip="这一阶段拿到 Skill 生产、脚本迁移和浏览器适配首批能力。">
<span class="time-date">5/12</span>
<div class="time-title">Skill 与浏览器首批完成</div>
<div class="time-text">Skill 编写助手、脚本转 Skill、浏览器适配可用。</div>
</div>
<div class="time-card" data-tip="本地服务和硬件适配要给出可调用版本。">
<span class="time-date">5/14</span>
<div class="time-title">服务与硬件完成</div>
<div class="time-text">本地服务管理、硬件与机器人适配完成首版验收。</div>
</div>
<div class="time-card" data-tip="UI 在 5 月 18 日前必须从粗略页面变成可操作台。">
<span class="time-date">5/18</span>
<div class="time-title">UI 操作台完成</div>
<div class="time-text">任务、审批、服务、成果、回放都能操作或查看。</div>
</div>
<div class="time-card" data-tip="五月底只做验收和发布,不再扩功能。">
<span class="time-date">5/31</span>
<div class="time-title">上线验收完成</div>
<div class="time-text">发布包、测试报告、回滚方案、演示材料齐备。</div>
</div>
</div>
</section>
</main>
<script>
const tooltip = document.createElement("div");
tooltip.id = "sg-tooltip";
document.body.appendChild(tooltip);
let activeTipTarget = null;
function positionTooltip(event) {
const margin = 14;
const offset = 16;
const rect = tooltip.getBoundingClientRect();
let left = event.clientX + offset;
let top = event.clientY + offset;
if (left + rect.width + margin > window.innerWidth) {
left = Math.max(margin, event.clientX - rect.width - offset);
}
if (top + rect.height + margin > window.innerHeight) {
top = Math.max(margin, event.clientY - rect.height - offset);
}
tooltip.style.left = `${left}px`;
tooltip.style.top = `${top}px`;
}
function showTooltip(target, event) {
const text = target.dataset.tip;
if (!text) return;
activeTipTarget = target;
tooltip.textContent = text;
tooltip.classList.add("is-visible");
positionTooltip(event);
}
function hideTooltip() {
activeTipTarget = null;
tooltip.classList.remove("is-visible");
}
document.addEventListener("pointerover", (event) => {
const target = event.target.closest("[data-tip]");
if (target) showTooltip(target, event);
});
document.addEventListener("pointermove", (event) => {
if (activeTipTarget) positionTooltip(event);
});
document.addEventListener("pointerout", (event) => {
if (!activeTipTarget) return;
const next = event.relatedTarget;
if (!next || !activeTipTarget.contains(next)) hideTooltip();
});
document.querySelectorAll("[data-tip]").forEach((element) => {
element.setAttribute("tabindex", "0");
element.setAttribute("aria-label", element.dataset.tip);
element.addEventListener("focus", (event) => {
const rect = event.currentTarget.getBoundingClientRect();
showTooltip(event.currentTarget, {
clientX: rect.left + rect.width / 2,
clientY: rect.top + rect.height / 2,
});
});
element.addEventListener("blur", hideTooltip);
});
</script>
</body>
</html>