| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246 |
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>时差培养箱 - 培养全流程详图(三端联动·可拖拽)</title>
- <style>
- * { margin: 0; padding: 0; box-sizing: border-box; }
- :root {
- --operate-color: #4A90E2;
- --operate-bg: #EAF3FC;
- --control-color: #F39C12;
- --control-bg: #FEF5E7;
- --front-color: #9B59B6;
- --front-bg: #F5EEF8;
- --branch-color: #E67E22;
- --branch-bg: #FDF2E9;
- --error-color: #E74C3C;
- --error-bg: #FDEDEC;
- --start-color: #16A085;
- --start-bg: #E8F8F5;
- --bg-page: #F4F6F8;
- --text-primary: #2C3E50;
- --text-secondary: #7F8C8D;
- --border-light: #E0E4E8;
- }
- html, body {
- font-family: "Microsoft YaHei", "PingFang SC", sans-serif;
- background: var(--bg-page);
- color: var(--text-primary);
- height: 100%;
- overflow: hidden;
- }
- /* 顶部栏 */
- .top-bar {
- position: fixed;
- top: 0; left: 0; right: 0;
- height: 60px;
- z-index: 1000;
- background: #fff;
- border-bottom: 1px solid var(--border-light);
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 0 24px;
- box-shadow: 0 2px 8px rgba(0,0,0,0.06);
- }
- .top-title { font-size: 20px; font-weight: 700; color: var(--operate-color); }
- .top-actions { display: flex; gap: 12px; align-items: center; }
- .btn {
- padding: 7px 16px; border: 1px solid var(--border-light);
- background: #fff; border-radius: 8px; cursor: pointer;
- font-size: 13px; color: var(--text-secondary); transition: all .2s;
- }
- .btn:hover { border-color: var(--operate-color); color: var(--operate-color); }
- .btn.primary { background: var(--operate-color); color: #fff; border-color: var(--operate-color); }
- .btn.primary:hover { background: #357ABD; }
- /* 图例 */
- .legend { display: flex; gap: 16px; font-size: 12px; }
- .legend-item { display: flex; align-items: center; gap: 5px; }
- .legend-dot { width: 11px; height: 11px; border-radius: 3px; }
- .legend-line { width: 20px; height: 0; border-top-width: 2px; border-top-style: solid; }
- /* 画布区域(可平移缩放) */
- .canvas-viewport {
- position: fixed;
- top: 60px; left: 0; right: 0; bottom: 0;
- overflow: hidden;
- cursor: grab;
- background:
- radial-gradient(circle, #dde3e8 1px, transparent 1px);
- background-size: 24px 24px;
- }
- .canvas-viewport.panning { cursor: grabbing; }
- .canvas-world {
- position: absolute;
- top: 0; left: 0;
- transform-origin: 0 0;
- }
- .svg-lines {
- position: absolute;
- top: 0; left: 0;
- overflow: visible;
- pointer-events: none;
- z-index: 1;
- }
- /* 节点 */
- .flow-node {
- position: absolute;
- width: 200px;
- padding: 12px 16px;
- background: #fff;
- border: 2px solid;
- border-radius: 10px;
- cursor: move;
- z-index: 2;
- box-shadow: 0 2px 6px rgba(0,0,0,0.08);
- transition: box-shadow .15s, transform .15s;
- user-select: none;
- }
- .flow-node:hover { box-shadow: 0 6px 16px rgba(0,0,0,0.18); z-index: 5; }
- .flow-node.active { box-shadow: 0 0 0 3px rgba(74,144,226,0.4), 0 6px 16px rgba(0,0,0,0.2); z-index: 6; }
- .flow-node.dragging { opacity: 0.85; box-shadow: 0 10px 28px rgba(0,0,0,0.28); z-index: 100; }
- .flow-node.operate { border-color: var(--operate-color); background: var(--operate-bg); }
- .flow-node.control { border-color: var(--control-color); background: var(--control-bg); }
- .flow-node.front { border-color: var(--front-color); background: var(--front-bg); }
- .flow-node.branch { border-color: var(--branch-color); background: var(--branch-bg); border-style: dashed; }
- .flow-node.error { border-color: var(--error-color); background: var(--error-bg); }
- .flow-node.start { border-color: var(--start-color); background: var(--start-bg); }
- .node-head { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; }
- .node-icon { font-size: 20px; }
- .node-title { font-size: 14px; font-weight: 700; color: var(--text-primary); line-height: 1.3; }
- .node-tag {
- display: inline-block; font-size: 11px; padding: 1px 8px;
- border-radius: 8px; background: rgba(0,0,0,0.06); color: var(--text-secondary);
- }
- .node-brief { font-size: 11.5px; color: var(--text-secondary); margin-top: 4px; line-height: 1.4; }
- /* 缩放控制 */
- .zoom-ctrl {
- position: fixed; bottom: 24px; right: 24px; z-index: 900;
- display: flex; flex-direction: column; gap: 6px;
- background: #fff; border: 1px solid var(--border-light);
- border-radius: 10px; padding: 6px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);
- }
- .zoom-btn {
- width: 34px; height: 34px; border: none; background: #f5f7f8;
- border-radius: 6px; cursor: pointer; font-size: 18px; color: var(--text-primary);
- }
- .zoom-btn:hover { background: var(--operate-bg); }
- .zoom-label { text-align: center; font-size: 11px; color: var(--text-secondary); }
- /* 右侧详情面板 */
- .detail-backdrop {
- position: fixed; inset: 0; background: rgba(0,0,0,0.35);
- z-index: 1500; opacity: 0; pointer-events: none; transition: opacity .25s;
- }
- .detail-backdrop.show { opacity: 1; pointer-events: auto; }
- .detail-panel {
- position: fixed; top: 0; right: -640px; width: 640px; max-width: 92vw;
- height: 100vh; background: #fff; z-index: 1600;
- box-shadow: -4px 0 24px rgba(0,0,0,0.18); overflow-y: auto;
- transition: right .3s ease;
- }
- .detail-panel.show { right: 0; }
- .detail-header {
- position: sticky; top: 0; background: #fff; z-index: 10;
- padding: 18px 22px; border-bottom: 1px solid var(--border-light);
- display: flex; align-items: center; justify-content: space-between;
- }
- .detail-title-wrap { display: flex; align-items: center; gap: 10px; }
- .detail-title { font-size: 18px; font-weight: 700; }
- .detail-badge { font-size: 11px; padding: 2px 10px; border-radius: 10px; color: #fff; }
- .detail-close {
- width: 32px; height: 32px; border: none; background: var(--border-light);
- border-radius: 50%; cursor: pointer; font-size: 18px; color: var(--text-secondary);
- }
- .detail-close:hover { background: var(--error-color); color: #fff; }
- .detail-body { padding: 20px 22px 40px; }
- .detail-section { margin-bottom: 20px; }
- .section-title {
- font-size: 14px; font-weight: 700; color: var(--operate-color);
- margin-bottom: 10px; padding-bottom: 6px; border-bottom: 1px solid var(--border-light);
- display: flex; align-items: center; gap: 6px;
- }
- .section-content { font-size: 13px; line-height: 1.75; color: var(--text-primary); }
- .section-content ul { list-style: none; }
- .section-content li { padding: 3px 0 3px 18px; position: relative; }
- .section-content li:before {
- content: "▸"; position: absolute; left: 0; color: var(--operate-color);
- }
- .flow-arrow { color: var(--error-color); font-weight: 700; }
- .code-loc {
- background: #2C3E50; color: #7FDBFF; padding: 6px 10px; border-radius: 5px;
- font-family: Consolas, monospace; font-size: 12px; margin-top: 5px; word-break: break-all;
- }
- .cross-box {
- background: #FEF9E7; border: 1px solid #F9E79F; border-radius: 8px;
- padding: 12px 14px; line-height: 1.9;
- }
- </style>
- </head>
- <body>
- <!-- 顶部栏 -->
- <div class="top-bar">
- <div class="top-title">🧬 时差培养箱 · 培养全流程详图</div>
- <div class="legend">
- <span class="legend-item"><span class="legend-dot" style="background:var(--start-color)"></span>起止</span>
- <span class="legend-item"><span class="legend-dot" style="background:var(--operate-color)"></span>operate</span>
- <span class="legend-item"><span class="legend-dot" style="background:var(--control-color)"></span>control</span>
- <span class="legend-item"><span class="legend-dot" style="background:var(--front-color)"></span>front</span>
- <span class="legend-item"><span class="legend-dot" style="background:var(--branch-color)"></span>判断</span>
- <span class="legend-item"><span class="legend-dot" style="background:var(--error-color)"></span>异常</span>
- <span class="legend-item"><span class="legend-line" style="border-color:#7F8C8D"></span>本端</span>
- <span class="legend-item"><span class="legend-line" style="border-color:#16A085;border-top-style:dashed"></span>跨端</span>
- <span class="legend-item"><span class="legend-line" style="border-color:#E74C3C;border-top-style:dotted"></span>异常/回流</span>
- </div>
- <div class="top-actions">
- <button class="btn" id="btnReset">↺ 重置布局</button>
- <button class="btn" id="btnFit">⊡ 适应屏幕</button>
- <button class="btn primary" id="btnSave">💾 已自动保存</button>
- </div>
- </div>
- <!-- 画布 -->
- <div class="canvas-viewport" id="viewport">
- <div class="canvas-world" id="world">
- <svg class="svg-lines" id="svg"></svg>
- <!-- 节点由 JS 渲染 -->
- </div>
- </div>
- <!-- 缩放控制 -->
- <div class="zoom-ctrl">
- <button class="zoom-btn" id="zoomIn">+</button>
- <div class="zoom-label" id="zoomLabel">100%</div>
- <button class="zoom-btn" id="zoomOut">-</button>
- </div>
- <!-- 详情面板 -->
- <div class="detail-backdrop" id="backdrop"></div>
- <div class="detail-panel" id="panel">
- <div class="detail-header">
- <div class="detail-title-wrap">
- <span class="detail-title" id="panelTitle">节点详情</span>
- <span class="detail-badge" id="panelBadge"></span>
- </div>
- <button class="detail-close" id="panelClose">×</button>
- </div>
- <div class="detail-body" id="panelBody"></div>
- </div>
- <script src="dagre.min.js"></script>
- <script src="flow-data.js"></script>
- <script src="flow-render.js"></script>
- </body>
- </html>
|