Files
pj0235-eai_agentplatform/eai_ap_app/frontend/public/studio-design-mockup.html
T
eaiadmin 4e8817d768 init: 数字员工平台初始代码
包含前端(Vue3 + VueFlow 画布)、后端(Go)、文档体系。
- 工作台画布:节点拖放、连线模式、右键菜单、AI 助手
- 后端:连接器 API、专员种子数据
- 导航:左侧导航、工坊、市场、控制台
2026-08-18 20:19:58 +08:00

714 lines
35 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. 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>StudioPage 视觉设计稿</title>
<style>
/* ===== Design System Tokens ===== */
:root {
--bg: #FFFFFF;
--fg: #18181B;
--muted: #F4F4F5;
--muted-fg: #71717A;
--border: #E4E4E7;
--accent: #F4F4F5;
--ring: #18181B;
--input-color: #409EFF;
--connector-color: #10B981;
--action-color: #E6A23C;
--result-color: #8E6CF2;
--permission-color: #F56C6C;
--input-bg: rgba(64, 158, 255, 0.06);
--connector-bg: rgba(16, 185, 129, 0.06);
--action-bg: rgba(230, 162, 60, 0.06);
--result-bg: rgba(142, 108, 242, 0.06);
--permission-bg: rgba(245, 108, 108, 0.06);
--font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
--font-mono: 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace;
--sp-unit: 4px;
--sp-xs: 8px; --sp-sm: 12px; --sp-md: 16px; --sp-lg: 24px; --sp-xl: 32px;
--radius-sm: 6px; --radius-md: 8px; --radius-lg: 12px; --radius-xl: 16px; --radius-full: 9999px;
--shadow-sm: 0 1px 2px rgba(24,24,27,0.04);
--shadow-md: 0 4px 12px rgba(24,24,27,0.06);
--shadow-lg: 0 12px 28px rgba(24,24,27,0.08);
--shadow-xl: 0 20px 40px rgba(24,24,27,0.10);
--transition-fast: 0.12s ease;
--transition-normal: 0.2s ease;
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { font-size: 14px; -webkit-font-smoothing: antialiased; }
body { font-family: var(--font); color: var(--fg); background: var(--bg); line-height: 1.5; }
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: var(--muted-fg); }
/* ===== State Switcher Tabs ===== */
.state-tabs {
display: flex;
align-items: center;
gap: 2px;
padding: 8px 16px;
background: var(--muted);
border-bottom: 1px solid var(--border);
flex-shrink: 0;
font-size: 12px;
}
.state-tab {
padding: 6px 14px;
border-radius: var(--radius-sm);
border: none;
background: transparent;
color: var(--muted-fg);
cursor: pointer;
font-size: 12px;
font-weight: 500;
transition: all var(--transition-fast);
}
.state-tab:hover { color: var(--fg); background: rgba(24,24,27,0.04); }
.state-tab.active { color: var(--fg); background: var(--bg); box-shadow: var(--shadow-sm); }
.state-tab-badge {
margin-left: 6px;
padding: 1px 6px;
border-radius: var(--radius-full);
background: rgba(24,24,27,0.06);
color: var(--muted-fg);
font-size: 10px;
}
/* ===== App Layout ===== */
.app { display: flex; flex-direction: column; height: 100vh; overflow: hidden; }
/* ===== TOOLBAR (48px) ===== */
.toolbar {
display: flex; align-items: center; justify-content: space-between;
height: 48px; padding: 0 12px;
border-bottom: 1px solid var(--border);
background: var(--bg); flex-shrink: 0; user-select: none;
}
.toolbar-left { display: flex; align-items: center; gap: 8px; min-width: 0; }
.toolbar-back-btn {
display: inline-flex; align-items: center; gap: 4px;
padding: 6px 10px; border-radius: var(--radius-md);
font-size: 13px; font-weight: 500; color: var(--muted-fg);
background: transparent; border: none; cursor: pointer;
transition: all var(--transition-fast);
}
.toolbar-back-btn:hover { background: var(--muted); color: var(--fg); }
.toolbar-divider { width: 1px; height: 20px; background: var(--border); flex-shrink: 0; }
.toolbar-title {
font-size: 14px; font-weight: 600; color: var(--fg);
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 240px;
letter-spacing: -0.01em;
}
.toolbar-stats { display: flex; align-items: center; gap: 4px; margin-left: 4px; }
.stat-pill {
display: inline-flex; align-items: center; gap: 4px;
padding: 2px 8px; border-radius: var(--radius-full);
background: var(--muted); color: var(--muted-fg);
font-size: 11px; font-weight: 500; line-height: 1.4;
}
.stat-pill .dot { width: 5px; height: 5px; border-radius: 50%; }
.toolbar-right { display: flex; align-items: center; gap: 4px; }
.tb-btn {
display: inline-flex; align-items: center; justify-content: center; gap: 4px;
padding: 6px 12px; border-radius: var(--radius-md);
font-size: 13px; font-weight: 500; border: none; cursor: pointer;
transition: all var(--transition-fast); line-height: 1.4;
}
.tb-btn-ghost { background: transparent; color: var(--muted-fg); }
.tb-btn-ghost:hover { background: var(--muted); color: var(--fg); }
.tb-btn-primary { background: var(--fg); color: var(--bg); }
.tb-btn-primary:hover { background: #3f3f46; }
.tb-btn-primary:active { transform: scale(0.97); }
/* ===== MAIN ===== */
.main { display: flex; flex: 1; min-height: 0; overflow: hidden; }
/* ===== SIDEBAR ===== */
.sidebar {
display: flex; flex-direction: column;
width: 200px; border-right: 1px solid var(--border);
background: var(--bg); flex-shrink: 0; overflow: hidden;
transition: width var(--transition-normal);
}
.sidebar.collapsed { width: 52px; }
.sidebar-header {
display: flex; align-items: center; justify-content: space-between;
padding: var(--sp-sm) var(--sp-sm); border-bottom: 1px solid var(--border);
flex-shrink: 0; height: 40px;
}
.sidebar-title { font-size: 13px; font-weight: 600; color: var(--muted-fg); white-space: nowrap; overflow: hidden; }
.sidebar-toggle {
display: inline-flex; align-items: center; justify-content: center;
width: 24px; height: 24px; border-radius: var(--radius-sm);
border: none; background: transparent; color: var(--muted-fg);
cursor: pointer; font-size: 12px; transition: all var(--transition-fast); flex-shrink: 0;
}
.sidebar-toggle:hover { background: var(--muted); color: var(--fg); }
.sidebar-items {
display: flex; flex-direction: column; gap: 2px;
padding: 8px 8px; overflow-y: auto; flex: 1;
}
.sidebar-item {
display: flex; align-items: center; gap: 10px;
padding: 8px 10px; border-radius: var(--radius-md);
cursor: grab; user-select: none;
transition: all var(--transition-fast);
border-left: 3px solid transparent;
}
.sidebar-item:hover { background: var(--muted); }
.sidebar-item .icon { font-size: 16px; flex-shrink: 0; width: 20px; text-align: center; }
.sidebar-item .label {
font-size: 13px; font-weight: 500; color: var(--fg);
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sidebar.collapsed .sidebar-item { justify-content: center; padding: 8px 4px; border-left-width: 0; }
.sidebar.collapsed .sidebar-item .label { display: none; }
.sidebar.collapsed .sidebar-header .sidebar-title { display: none; }
.sidebar.collapsed .sidebar-header { justify-content: center; padding: 8px 4px; }
.sidebar-item.type-input { border-left-color: var(--input-color); }
.sidebar-item.type-connector { border-left-color: var(--connector-color); }
.sidebar-item.type-action { border-left-color: var(--action-color); }
.sidebar-item.type-result { border-left-color: var(--result-color); }
.sidebar-item.type-permission { border-left-color: var(--permission-color); }
.sidebar-item .color-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; display: none; }
.sidebar.collapsed .sidebar-item .color-dot { display: block; }
.sidebar.collapsed .sidebar-item .icon { display: none; }
.sidebar-item.type-input .color-dot { background: var(--input-color); }
.sidebar-item.type-connector .color-dot { background: var(--connector-color); }
.sidebar-item.type-action .color-dot { background: var(--action-color); }
.sidebar-item.type-result .color-dot { background: var(--result-color); }
.sidebar-item.type-permission .color-dot { background: var(--permission-color); }
.sidebar-item .drag-hint { display: none; font-size: 10px; color: var(--muted-fg); margin-left: auto; }
.sidebar-item:hover .drag-hint { display: block; }
.sidebar.collapsed .sidebar-item .drag-hint { display: none !important; }
/* ===== CANVAS ===== */
.canvas-area { flex: 1; min-width: 0; display: flex; flex-direction: column; overflow: hidden; position: relative; }
.canvas-container {
flex: 1; position: relative; overflow: hidden;
background: radial-gradient(circle, #D4D4D8 1px, transparent 1px);
background-size: 20px 20px; background-color: #FAFAFA;
}
.canvas-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; pointer-events: none; }
.canvas-hint {
font-size: 13px; color: var(--muted-fg);
background: rgba(255,255,255,0.85); backdrop-filter: blur(8px);
padding: 8px 16px; border-radius: var(--radius-full);
border: 1px solid var(--border); box-shadow: var(--shadow-sm);
pointer-events: none;
}
.canvas-controls {
position: absolute; top: 12px; right: 12px;
display: flex; gap: 4px; padding: 4px;
border-radius: var(--radius-lg); background: rgba(255,255,255,0.92);
backdrop-filter: blur(8px); border: 1px solid var(--border);
box-shadow: var(--shadow-md); z-index: 5;
}
.canvas-controls button {
display: inline-flex; align-items: center; justify-content: center; gap: 4px;
padding: 6px 10px; border-radius: var(--radius-md);
font-size: 12px; font-weight: 500; color: var(--fg);
background: transparent; border: none; cursor: pointer;
transition: all var(--transition-fast);
}
.canvas-controls button:hover { background: var(--muted); }
/* Demo nodes */
.demo-nodes { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
.demo-node {
position: absolute; min-width: 180px; max-width: 220px;
padding: 14px 16px; border-radius: var(--radius-xl);
background: var(--bg); border: 1px solid var(--border);
box-shadow: var(--shadow-lg); cursor: pointer;
transition: box-shadow var(--transition-fast), border-color var(--transition-fast);
}
.demo-node:hover { box-shadow: var(--shadow-xl); }
.demo-node.selected { border-color: var(--ring); box-shadow: 0 0 0 2px rgba(24,24,27,0.08), var(--shadow-lg); }
.demo-node .node-type { font-size: 11px; font-weight: 500; color: var(--muted-fg); text-transform: uppercase; letter-spacing: 0.04em; }
.demo-node .node-title { margin-top: 6px; font-size: 14px; font-weight: 600; color: var(--fg); line-height: 1.4; }
.demo-node .node-desc { margin-top: 6px; font-size: 12px; color: var(--muted-fg); line-height: 1.5; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.demo-node.type-input { background: var(--input-bg); border-color: rgba(64,158,255,0.2); }
.demo-node.type-input.selected { border-color: var(--input-color); box-shadow: 0 0 0 2px rgba(64,158,255,0.12), var(--shadow-lg); }
.demo-node.type-connector { background: var(--connector-bg); border-color: rgba(16,185,129,0.2); }
.demo-node.type-connector.selected { border-color: var(--connector-color); box-shadow: 0 0 0 2px rgba(16,185,129,0.12), var(--shadow-lg); }
.demo-node.type-action { background: var(--action-bg); border-color: rgba(230,162,60,0.2); }
.demo-node.type-action.selected { border-color: var(--action-color); box-shadow: 0 0 0 2px rgba(230,162,60,0.12), var(--shadow-lg); }
.demo-node.type-result { background: var(--result-bg); border-color: rgba(142,108,242,0.2); }
.demo-node.type-result.selected { border-color: var(--result-color); box-shadow: 0 0 0 2px rgba(142,108,242,0.12), var(--shadow-lg); }
/* ===== INSPECTOR (right, 280px) ===== */
.inspector {
width: 280px; border-left: 1px solid var(--border);
background: var(--bg); flex-shrink: 0;
display: flex; flex-direction: column; overflow: hidden;
}
.inspector-header {
padding: var(--sp-sm) var(--sp-md); border-bottom: 1px solid var(--border);
flex-shrink: 0; height: 40px; display: flex; align-items: center;
}
.inspector-header h3 { font-size: 13px; font-weight: 600; color: var(--muted-fg); }
.inspector-content { flex: 1; overflow-y: auto; padding: var(--sp-md); }
.inspector-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; gap: 12px; padding: var(--sp-xl); text-align: center; }
.inspector-empty .empty-icon { font-size: 32px; opacity: 0.3; }
.inspector-empty .empty-text { font-size: 13px; color: var(--muted-fg); line-height: 1.5; max-width: 200px; }
.inspector-section { margin-bottom: 20px; }
.inspector-section-title { font-size: 11px; font-weight: 600; color: var(--muted-fg); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 10px; }
.inspector-field { margin-bottom: 14px; }
.inspector-field:last-child { margin-bottom: 0; }
.inspector-field-label { font-size: 11px; font-weight: 500; color: var(--muted-fg); margin-bottom: 4px; }
.inspector-field-value { font-size: 13px; color: var(--fg); padding: 6px 10px; border-radius: var(--radius-sm); background: var(--muted); line-height: 1.5; word-break: break-word; }
.inspector-field-input { width: 100%; padding: 6px 10px; border-radius: var(--radius-sm); border: 1px solid var(--border); background: var(--bg); font-size: 13px; font-family: var(--font); color: var(--fg); outline: none; transition: border-color var(--transition-fast); }
.inspector-field-input:focus { border-color: var(--ring); box-shadow: 0 0 0 1px var(--ring); }
.inspector-field-textarea { width: 100%; padding: 6px 10px; border-radius: var(--radius-sm); border: 1px solid var(--border); background: var(--bg); font-size: 13px; font-family: var(--font); color: var(--fg); outline: none; resize: vertical; min-height: 72px; line-height: 1.5; transition: border-color var(--transition-fast); }
.inspector-field-textarea:focus { border-color: var(--ring); box-shadow: 0 0 0 1px var(--ring); }
.inspector-relation { display: flex; align-items: center; gap: 6px; padding: 6px 10px; border-radius: var(--radius-sm); background: var(--muted); font-size: 12px; color: var(--muted-fg); }
.inspector-relation .arrow { color: var(--border); font-size: 12px; }
.inspector-relation .rel-value { color: var(--fg); font-weight: 500; }
.position-badge { display: inline-flex; align-items: center; gap: 8px; padding: 4px 10px; border-radius: var(--radius-sm); background: var(--muted); font-size: 12px; font-family: var(--font-mono); color: var(--muted-fg); }
/* ===== LANDING ===== */
.landing { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--sp-lg); padding: var(--sp-xl); background: var(--bg); }
.landing h1 { font-size: 20px; font-weight: 700; color: var(--fg); letter-spacing: -0.02em; margin-bottom: 4px; }
.landing p { font-size: 14px; color: var(--muted-fg); max-width: 400px; text-align: center; line-height: 1.6; }
.landing-cards { display: flex; gap: 24px; margin-top: 12px; }
.landing-card {
width: 200px; padding: 32px 24px; border-radius: var(--radius-xl);
border: 1px solid var(--border); background: var(--bg);
box-shadow: var(--shadow-md); cursor: pointer; text-align: center;
transition: all var(--transition-fast);
}
.landing-card:hover { border-color: rgba(24,24,27,0.15); box-shadow: var(--shadow-lg); transform: translateY(-2px); }
.landing-card:active { transform: scale(0.98); }
.landing-card .card-icon { font-size: 36px; margin-bottom: 16px; }
.landing-card .card-title { font-size: 15px; font-weight: 600; color: var(--fg); margin-bottom: 8px; }
.landing-card .card-desc { font-size: 13px; color: var(--muted-fg); line-height: 1.5; }
/* ===== DIALOG OVERLAY ===== */
.dialog-overlay {
display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(24,24,27,0.4); backdrop-filter: blur(2px);
z-index: 100; align-items: center; justify-content: center;
}
.dialog-overlay.open { display: flex; }
.dialog {
background: var(--bg); border-radius: var(--radius-xl);
box-shadow: var(--shadow-xl); max-height: 80vh; overflow-y: auto;
animation: dialogIn 0.15s ease; min-width: 520px; max-width: 680px;
}
@keyframes dialogIn { from { opacity: 0; transform: scale(0.96) translateY(4px); } to { opacity: 1; transform: scale(1) translateY(0); } }
.dialog-header { display: flex; align-items: center; justify-content: space-between; padding: var(--sp-md) var(--sp-lg); border-bottom: 1px solid var(--border); }
.dialog-header h2 { font-size: 16px; font-weight: 600; color: var(--fg); }
.dialog-close { width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; border-radius: var(--radius-sm); border: none; background: transparent; color: var(--muted-fg); cursor: pointer; font-size: 16px; transition: all var(--transition-fast); }
.dialog-close:hover { background: var(--muted); color: var(--fg); }
.dialog-body { padding: var(--sp-lg); }
.dialog-footer { display: flex; justify-content: flex-end; gap: 8px; padding: var(--sp-md) var(--sp-lg); border-top: 1px solid var(--border); }
.template-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
.template-card { padding: 16px; border-radius: var(--radius-lg); border: 1px solid var(--border); background: var(--muted); cursor: pointer; transition: all var(--transition-fast); }
.template-card:hover { border-color: rgba(24,24,27,0.2); box-shadow: var(--shadow-sm); }
.template-card.active { border-color: var(--fg); background: var(--bg); box-shadow: 0 0 0 1px var(--fg); }
.template-card .tc-title { font-size: 14px; font-weight: 600; color: var(--fg); margin-bottom: 4px; }
.template-card .tc-summary { font-size: 12px; color: var(--muted-fg); line-height: 1.5; margin-bottom: 10px; }
.template-card .tc-tags { display: flex; flex-wrap: wrap; gap: 4px; }
.template-card .tc-tag { display: inline-flex; padding: 2px 8px; border-radius: var(--radius-full); background: rgba(24,24,27,0.06); color: var(--muted-fg); font-size: 11px; line-height: 1.4; }
.settings-form { display: flex; flex-direction: column; gap: 16px; }
.sf-field { display: flex; flex-direction: column; gap: 6px; }
.sf-label { font-size: 13px; font-weight: 500; color: var(--fg); }
.sf-input { padding: 8px 12px; border-radius: var(--radius-md); border: 1px solid var(--border); background: var(--bg); font-size: 14px; font-family: var(--font); color: var(--fg); outline: none; transition: border-color var(--transition-fast); }
.sf-input:focus { border-color: var(--ring); box-shadow: 0 0 0 1px var(--ring); }
.sf-select { padding: 8px 12px; border-radius: var(--radius-md); border: 1px solid var(--border); background: var(--bg); font-size: 14px; font-family: var(--font); color: var(--fg); outline: none; cursor: pointer; transition: border-color var(--transition-fast); }
.sf-select:focus { border-color: var(--ring); box-shadow: 0 0 0 1px var(--ring); }
.sf-textarea { padding: 8px 12px; border-radius: var(--radius-md); border: 1px solid var(--border); background: var(--bg); font-size: 14px; font-family: var(--font); color: var(--fg); outline: none; resize: vertical; min-height: 64px; line-height: 1.5; transition: border-color var(--transition-fast); }
.sf-textarea:focus { border-color: var(--ring); box-shadow: 0 0 0 1px var(--ring); }
.sf-pills { display: flex; gap: 6px; flex-wrap: wrap; }
.sf-pill { padding: 4px 10px; border-radius: var(--radius-full); background: rgba(24,24,27,0.06); color: var(--muted-fg); font-size: 12px; font-weight: 500; }
.btn {
display: inline-flex; align-items: center; justify-content: center; gap: 6px;
padding: 8px 16px; border-radius: var(--radius-md);
font-size: 13px; font-weight: 500; border: none; cursor: pointer;
transition: all var(--transition-fast); line-height: 1.4;
}
.btn:hover { opacity: 0.9; }
.btn:active { transform: scale(0.97); }
.btn-primary { background: var(--fg); color: var(--bg); }
.btn-primary:hover { background: #3f3f46; }
.btn-ghost { background: transparent; color: var(--fg); }
.btn-ghost:hover { background: var(--muted); }
.annotation {
padding: 8px 16px; background: var(--muted); border-top: 1px solid var(--border);
font-size: 11px; color: var(--muted-fg);
display: flex; justify-content: space-between;
flex-shrink: 0;
}
@media (max-width: 1100px) {
.landing-cards { flex-direction: column; align-items: center; }
.sidebar { width: 200px; }
.sidebar.collapsed { width: 200px; }
.inspector { width: 100%; border-left: none; border-top: 1px solid var(--border); }
.template-grid { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<div class="app">
<!-- ====== State Switcher (demo only) ====== -->
<div class="state-tabs">
<span style="font-size: 11px; font-weight: 600; color: var(--muted-fg); margin-right: 8px; text-transform: uppercase; letter-spacing: 0.04em;">切换视图</span>
<button class="state-tab active" data-state="canvas-selected" onclick="switchState('canvas-selected')">
🎯 画布模式(选中节点)
</button>
<button class="state-tab" data-state="canvas-empty" onclick="switchState('canvas-empty')">
🖱 画布模式(无选中)
</button>
<button class="state-tab" data-state="landing" onclick="switchState('landing')">
🏠 新手引导
</button>
<button class="state-tab" data-state="settings" onclick="switchState('settings')">
⚙ 设置弹窗
</button>
<button class="state-tab" data-state="sidebar-collapsed" onclick="switchState('sidebar-collapsed')">
◀ 侧栏折叠
</button>
<span style="flex: 1;"></span>
<span style="font-size: 10px; color: var(--muted-fg);">参考 Langflow / Dify / Coze 设计体系</span>
</div>
<!-- ====== TOOLBAR ====== -->
<div class="toolbar">
<div class="toolbar-left">
<button class="toolbar-back-btn"><span style="font-size:14px;">←</span> 工坊</button>
<div class="toolbar-divider"></div>
<span class="toolbar-title">邮箱简历整理 DW</span>
<div class="toolbar-stats">
<span class="stat-pill"><span class="dot" style="background:var(--input-color);"></span> 3 节点</span>
<span class="stat-pill"><span class="dot" style="background:var(--connector-color);"></span> 2 边</span>
</div>
</div>
<div class="toolbar-right">
<button class="tb-btn tb-btn-ghost">⚙ 设置</button>
<button class="tb-btn tb-btn-primary">保存</button>
</div>
</div>
<!-- ====== MAIN CONTENT - Canvas View ====== -->
<div class="main" id="canvas-view">
<!-- Sidebar -->
<div class="sidebar" id="sidebar">
<div class="sidebar-header">
<span class="sidebar-title">节点库</span>
<button class="sidebar-toggle" onclick="toggleSidebar()" title="折叠侧栏">◀</button>
</div>
<div class="sidebar-items">
<div class="sidebar-item type-input">
<span class="icon">📥</span>
<span class="color-dot"></span>
<span class="label">信源</span>
<span class="drag-hint">拖入画布</span>
</div>
<div class="sidebar-item type-connector">
<span class="icon">🔗</span>
<span class="color-dot"></span>
<span class="label">连接器</span>
<span class="drag-hint">拖入画布</span>
</div>
<div class="sidebar-item type-action">
<span class="icon">⚡</span>
<span class="color-dot"></span>
<span class="label">动作</span>
<span class="drag-hint">拖入画布</span>
</div>
<div class="sidebar-item type-result">
<span class="icon">📤</span>
<span class="color-dot"></span>
<span class="label">结果</span>
<span class="drag-hint">拖入画布</span>
</div>
<div class="sidebar-item type-permission">
<span class="icon">🔒</span>
<span class="color-dot"></span>
<span class="label">权限</span>
<span class="drag-hint">拖入画布</span>
</div>
</div>
</div>
<!-- Canvas -->
<div class="canvas-area">
<div class="canvas-container" id="canvas-bg">
<!-- Demo nodes -->
<div class="demo-nodes">
<div class="demo-node type-input selected" style="left:80px; top:80px;">
<div class="node-type">信源</div>
<div class="node-title">招聘邮箱</div>
<div class="node-desc">拉取收件箱的简历投递邮件</div>
</div>
<div class="demo-node type-connector" style="left:340px; top:80px;">
<div class="node-type">连接器</div>
<div class="node-title">163 邮箱 IMAP</div>
<div class="node-desc">已绑定 IMAP 协议和认证</div>
</div>
<div class="demo-node type-action" style="left:600px; top:80px;">
<div class="node-type">动作</div>
<div class="node-title">识别简历邮件</div>
<div class="node-desc">抽取候选人信息和附件</div>
</div>
<div class="demo-node type-result" style="left:860px; top:80px;">
<div class="node-type">结果</div>
<div class="node-title">简历表</div>
<div class="node-desc">写入企微智能表格</div>
</div>
</div>
<!-- SVG edges -->
<svg style="position:absolute; top:0; left:0; width:100%; height:100%; pointer-events:none; z-index:0;">
<defs>
<marker id="arrow" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto">
<polygon points="0 0, 8 3, 0 6" fill="#A1A1AA"/>
</marker>
</defs>
<path d="M 260 145 C 290 145, 310 145, 340 145" stroke="#A1A1AA" stroke-width="2" fill="none" marker-end="url(#arrow)"/>
<path d="M 520 145 C 550 145, 570 145, 600 145" stroke="#A1A1AA" stroke-width="2" fill="none" marker-end="url(#arrow)"/>
<path d="M 780 145 C 810 145, 830 145, 860 145" stroke="#A1A1AA" stroke-width="2" fill="none" marker-end="url(#arrow)"/>
</svg>
<div class="canvas-controls">
<button>⟲ 重置</button>
<button>⊞ 适配</button>
</div>
<div class="canvas-overlay" id="canvas-overlay">
<div class="canvas-hint">从左侧拖入节点,或点击节点编辑</div>
</div>
</div>
</div>
<!-- Inspector (populated) -->
<div class="inspector" id="inspector">
<div class="inspector-header"><h3>节点检查器</h3></div>
<div class="inspector-content" id="inspector-content">
<!-- Populated state: shown when node selected -->
<div id="inspector-filled">
<div class="inspector-section">
<div class="inspector-section-title">节点信息</div>
<div class="inspector-field">
<div class="inspector-field-label">当前节点</div>
<div class="inspector-field-value" style="display:flex; align-items:center; gap:6px;">
<span style="width:8px; height:8px; border-radius:50%; background:var(--input-color); flex-shrink:0;"></span>
<span>招聘邮箱</span>
</div>
</div>
<div class="inspector-field">
<div class="inspector-field-label">节点类型</div>
<div class="inspector-field-value">信源 · Input</div>
</div>
<div class="inspector-field">
<div class="inspector-field-label">节点标题</div>
<input class="inspector-field-input" type="text" value="招聘邮箱" placeholder="输入节点标题" />
</div>
<div class="inspector-field">
<div class="inspector-field-label">节点说明</div>
<textarea class="inspector-field-textarea" placeholder="输入节点说明">拉取收件箱的简历投递邮件,解析附件中的候选人信息。</textarea>
</div>
<div class="inspector-field">
<div class="inspector-field-label">节点位置</div>
<div class="position-badge"><span>X 80</span><span style="color:var(--border);">/</span><span>Y 80</span></div>
</div>
</div>
<div class="inspector-section">
<div class="inspector-section-title">连接关系</div>
<div class="inspector-field">
<div class="inspector-field-label">上游节点</div>
<div class="inspector-relation"><span class="rel-value">—</span><span class="arrow">←</span><span style="color:var(--muted-fg);">无上游</span></div>
</div>
<div class="inspector-field">
<div class="inspector-field-label">下游节点</div>
<div class="inspector-relation"><span class="rel-value">163 邮箱 IMAP</span><span class="arrow">→</span><span style="color:var(--muted-fg);">连接器</span></div>
</div>
</div>
</div>
<!-- Empty state: shown when no node selected -->
<div id="inspector-empty" style="display:none;">
<div class="inspector-empty">
<div class="empty-icon">🖱</div>
<div class="empty-text">点击画布中的节点,<br>右侧将显示该节点的属性和连接关系。</div>
</div>
</div>
</div>
</div>
</div>
<!-- ====== LANDING VIEW (hidden by default) ====== -->
<div class="main" id="landing-view" style="display:none;">
<div class="landing">
<h1>DW 工坊</h1>
<p>把信源、连接器、动作、结果、权限装配成一个可运行的数字员工。</p>
<div class="landing-cards">
<div class="landing-card">
<div class="card-icon">📄</div>
<div class="card-title">空白创建</div>
<div class="card-desc">从零开始,先搭主干再补细节。</div>
</div>
<div class="landing-card">
<div class="card-icon">📋</div>
<div class="card-title">从模板创建</div>
<div class="card-desc">套一个标准骨架,再到画布里微调。</div>
</div>
<div class="landing-card">
<div class="card-icon">📂</div>
<div class="card-title">从已有复制</div>
<div class="card-desc">复制一个现有的 DW,再按需调整。</div>
</div>
</div>
</div>
</div>
</div>
<!-- ====== SETTINGS DIALOG OVERLAY ====== -->
<div class="dialog-overlay" id="settings-dialog">
<div class="dialog" style="min-width:520px; max-width:520px;">
<div class="dialog-header">
<h2>DW 设置</h2>
<button class="dialog-close" onclick="closeSettings()">✕</button>
</div>
<div class="dialog-body">
<div class="settings-form">
<div class="sf-field">
<div class="sf-label">DW 名称</div>
<input class="sf-input" type="text" value="邮箱简历整理 DW" placeholder="例如:邮箱简历整理 DW" />
</div>
<div class="sf-field">
<div class="sf-label">DW Key</div>
<input class="sf-input" type="text" value="email-resume-dw" placeholder="例如:email-resume-dw" />
</div>
<div class="sf-field">
<div class="sf-label">路由</div>
<input class="sf-input" type="text" value="/workbench/email-resume-dw" placeholder="/workbench/email-resume-dw" />
</div>
<div class="sf-field">
<div class="sf-label">工作形态</div>
<select class="sf-select">
<option selected>DW</option>
<option>ADW</option>
</select>
</div>
<div class="sf-field">
<div class="sf-label">摘要</div>
<textarea class="sf-textarea" placeholder="用一句话描述这个 DW 的任务目标">从招聘邮箱抓取简历,整理候选人后写入企微智能表格。</textarea>
</div>
<div class="sf-field">
<div class="sf-label">编译预览</div>
<div class="sf-pills">
<span class="sf-pill">信源 1</span>
<span class="sf-pill" style="background:var(--action-bg); color:#b8842c;">动作 3</span>
<span class="sf-pill" style="background:var(--result-bg); color:#6b48c9;">结果 1</span>
<span class="sf-pill" style="background:var(--permission-bg); color:#d1544c;">权限 2</span>
</div>
<div style="margin-top:8px; font-size:12px; color:var(--muted-fg);">连接器范围:163 邮箱 IMAP</div>
</div>
</div>
</div>
<div class="dialog-footer">
<button class="btn btn-ghost" onclick="closeSettings()">取消</button>
<button class="btn btn-primary" onclick="closeSettings()">确认</button>
</div>
</div>
</div>
<!-- ====== Annotation Footer ====== -->
<div class="annotation">
<span>StudioPage 视觉设计稿 · 顶部标签可切换不同状态</span>
<span>间距 4px 网格 · 锌灰单色系统 + 5 色语义节点</span>
</div>
<script>
function switchState(state) {
// Update tabs
document.querySelectorAll('.state-tab').forEach(t => t.classList.remove('active'));
document.querySelector(`.state-tab[data-state="${state}"]`).classList.add('active');
const canvasView = document.getElementById('canvas-view');
const landingView = document.getElementById('landing-view');
const settingsDialog = document.getElementById('settings-dialog');
const sidebar = document.getElementById('sidebar');
const inspectorContent = document.getElementById('inspector-content');
const inspectorFilled = document.getElementById('inspector-filled');
const inspectorEmpty = document.getElementById('inspector-empty');
// Reset
canvasView.style.display = 'flex';
landingView.style.display = 'none';
settingsDialog.classList.remove('open');
sidebar.classList.remove('collapsed');
inspectorFilled.style.display = 'block';
inspectorEmpty.style.display = 'none';
switch (state) {
case 'canvas-selected':
canvasView.style.display = 'flex';
landingView.style.display = 'none';
inspectorFilled.style.display = 'block';
inspectorEmpty.style.display = 'none';
break;
case 'canvas-empty':
canvasView.style.display = 'flex';
landingView.style.display = 'none';
inspectorFilled.style.display = 'none';
inspectorEmpty.style.display = 'block';
break;
case 'landing':
canvasView.style.display = 'none';
landingView.style.display = 'flex';
break;
case 'settings':
canvasView.style.display = 'flex';
landingView.style.display = 'none';
settingsDialog.classList.add('open');
break;
case 'sidebar-collapsed':
canvasView.style.display = 'flex';
landingView.style.display = 'none';
sidebar.classList.add('collapsed');
inspectorFilled.style.display = 'block';
inspectorEmpty.style.display = 'none';
break;
}
}
function toggleSidebar() {
const sidebar = document.getElementById('sidebar');
const toggle = sidebar.querySelector('.sidebar-toggle');
sidebar.classList.toggle('collapsed');
toggle.textContent = sidebar.classList.contains('collapsed') ? '▶' : '◀';
}
function closeSettings() {
document.getElementById('settings-dialog').classList.remove('open');
// Switch back to canvas-selected tab
switchState('canvas-selected');
}
</script>
</body>
</html>