Files
Z User e45b68bb8b feat: IMAP mail sync with thread-to-contact matching (PRD §6.1)
Real IMAP connection via imapflow library:
- Connects to IMAP server (IMAPFlow, TLS on port 993)
- Fetches recent messages (configurable limit, default 20)
- Extracts: from, fromEmail, subject, date, snippet (200 chars)
- Thread-to-contact matching (PRD §6.1):
  1. Match sender email against persons table
  2. If no person match, extract domain and match against company.website
  3. Returns matchedCompany + matchedPerson for each email
- Audit-logged (action: crm.mailsync:imap)
- Zod validated (host, port, user, password, folder, limit)
- 502 on IMAP connection failure with detail
- 400 on validation failure

Tested: invalid creds → 502 'IMAP connection failed', missing host → 400
2026-10-27 22:50:50 +00:00

1293 lines
55 KiB
HTML
Executable File
Raw Permalink 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=1440" />
<title>动态流 · Atelier 工作空间</title>
<style>
:root{
--bg:#EFEFF1;
--bg-2:#F6F6F8;
--card:#FFFFFF;
--card-soft:#F1F1F3;
--line:rgba(15,15,20,.06);
--ink:#15161A;
--ink-2:#5B5C63;
--ink-3:#9A9BA3;
--green:#0F8B5C;
--green-soft:#D9EFE4;
--coral:#FF5A2E;
--coral-soft:#FFE2D7;
--amber:#E9A23B;
--amber-soft:#FBE9C9;
--violet:#6B5BD9;
--violet-soft:#E6E2FA;
--ink-btn:#1B1D24;
--shadow-sm:0 1px 0 rgba(15,15,20,.04), 0 1px 2px rgba(15,15,20,.04);
--shadow-md:0 1px 0 rgba(15,15,20,.04), 0 8px 24px rgba(15,15,20,.06);
--shadow-lg:0 1px 0 rgba(15,15,20,.04), 0 18px 48px rgba(15,15,20,.10);
--radius-lg:22px;
--radius-md:16px;
--radius-sm:12px;
--radius-pill:999px;
font-family:'Inter', 'Helvetica Neue', 'PingFang SC', 'Microsoft YaHei', -apple-system, system-ui, sans-serif;
}
*{box-sizing:border-box}
html,body{margin:0;padding:0;background:var(--bg);color:var(--ink);}
body{
font-feature-settings:"ss01","cv11";
font-size:14px;line-height:1.55;
-webkit-font-smoothing:antialiased;
background:
radial-gradient(900px 600px at 0% 0%, #E5E0F4 0%, rgba(229,224,244,0) 60%),
radial-gradient(800px 600px at 100% 0%, #FBE6DA 0%, rgba(251,230,218,0) 55%),
var(--bg);
min-height:100vh;
}
.frame{max-width:1320px;margin:0 auto;padding:28px 32px 56px;}
/* ---------- 顶部栏 ---------- */
.topbar{
display:flex;align-items:center;gap:16px;
padding:10px 14px;border-radius:var(--radius-pill);
background:rgba(255,255,255,.6);
backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);
box-shadow:var(--shadow-sm);
}
.brand{display:flex;align-items:center;gap:10px;padding:4px 10px;cursor:pointer}
.brand-mark{
width:26px;height:26px;border-radius:8px;
background:linear-gradient(135deg,#15161A,#3A3C44);
display:grid;place-items:center;color:#fff;font-weight:600;font-size:12px;
}
.brand-name{font-weight:600;letter-spacing:-.01em}
.crumbs{display:flex;align-items:center;gap:8px;color:var(--ink-3);font-size:13px}
.crumbs span{cursor:pointer}
.crumbs span.now{color:var(--ink)}
.topbar .spacer{flex:1}
.search{
display:flex;align-items:center;gap:8px;padding:8px 14px;
background:rgba(15,15,20,.04);border-radius:var(--radius-pill);
color:var(--ink-2);min-width:280px;cursor:text;
}
.search .kbd{margin-left:auto;color:var(--ink-3);font-size:11px;
padding:2px 6px;border-radius:6px;background:rgba(15,15,20,.06);}
.iconbtn{
width:36px;height:36px;border-radius:var(--radius-pill);
background:rgba(15,15,20,.04);display:grid;place-items:center;color:var(--ink-2);
position:relative;cursor:pointer;transition:background .15s, transform .08s;
}
.iconbtn:hover{background:rgba(15,15,20,.08)}
.iconbtn:active{transform:scale(.96)}
.iconbtn .dot{
position:absolute;top:6px;right:6px;width:8px;height:8px;border-radius:50%;
background:var(--coral);box-shadow:0 0 0 2px #fff;
}
.me{display:flex;align-items:center;gap:10px;padding:4px 10px 4px 4px;border-radius:var(--radius-pill);background:rgba(15,15,20,.04);cursor:pointer;transition:background .15s}
.me:hover{background:rgba(15,15,20,.08)}
.avatar{
width:28px;height:28px;border-radius:50%;
background-size:cover;background-position:center;background-color:#E6E6EA;
flex:none;
}
.avatar.sm{width:22px;height:22px}
.avatar.md{width:32px;height:32px}
.avatar.lg{width:44px;height:44px}
/* ---------- 二级导航 ---------- */
.subnav{display:flex;align-items:center;gap:18px;margin:22px 0 18px;}
.tabs{display:flex;gap:4px;padding:5px;border-radius:var(--radius-pill);background:rgba(255,255,255,.65);box-shadow:var(--shadow-sm);}
.tab{padding:8px 16px;border-radius:var(--radius-pill);color:var(--ink-2);font-size:13px;font-weight:500;cursor:pointer;transition:background .15s, color .15s}
.tab:hover:not(.active){background:rgba(15,15,20,.04);color:var(--ink)}
.tab.active{background:var(--ink-btn);color:#fff}
.tab .count{display:inline-block;margin-left:6px;padding:1px 7px;border-radius:999px;background:rgba(255,255,255,.18);font-size:11px;color:#fff}
.tab:not(.active) .count{background:rgba(15,15,20,.06);color:var(--ink-2)}
.subnav .right{margin-left:auto;display:flex;align-items:center;gap:10px}
.chip{
display:inline-flex;align-items:center;gap:8px;padding:8px 14px;
border-radius:var(--radius-pill);background:rgba(255,255,255,.65);
color:var(--ink-2);font-size:13px;box-shadow:var(--shadow-sm);
cursor:pointer;transition:background .15s, transform .08s;
}
.chip:hover{background:rgba(255,255,255,.85)}
.chip:active{transform:scale(.97)}
.chip.solid{background:var(--ink-btn);color:#fff}
.chip.solid:hover{background:#000}
.chip svg{flex:none}
/* ---------- 主进度卡 ---------- */
.hero{
background:var(--card-soft);
border-radius:var(--radius-lg);
padding:26px 28px 24px;
box-shadow:var(--shadow-sm);
position:relative;
}
.hero-head{display:flex;align-items:flex-start;gap:18px}
.hero-title{font-size:22px;font-weight:600;letter-spacing:-.01em;color:var(--ink)}
.hero-meta{margin-top:6px;color:var(--ink-2);font-size:13px;display:flex;align-items:center;gap:8px}
.hero-meta .play{
width:18px;height:18px;border-radius:50%;display:inline-grid;place-items:center;
background:rgba(15,15,20,.06);color:var(--ink);cursor:pointer;
}
.hero-actions{margin-left:auto;display:flex;gap:10px}
.btn{
display:inline-flex;align-items:center;gap:8px;padding:10px 16px;
border-radius:var(--radius-pill);font-size:13px;font-weight:500;cursor:pointer;
background:rgba(255,255,255,.7);color:var(--ink);box-shadow:var(--shadow-sm);
transition:background .15s, transform .08s;
}
.btn:hover{background:#fff}
.btn:active{transform:scale(.97)}
.btn.split{padding-right:6px}
.btn .caret{width:24px;height:24px;border-radius:50%;background:rgba(15,15,20,.06);display:grid;place-items:center;margin-left:4px}
.btn.primary{background:var(--ink-btn);color:#fff}
.btn.primary:hover{background:#000}
.btn.primary .caret{background:rgba(255,255,255,.15)}
.hero-progress{margin-top:30px}
.legend{display:flex;gap:32px;align-items:flex-end;}
.legend .item .label{font-size:12px;color:var(--ink-2);letter-spacing:.01em}
.legend .item.done .label{color:var(--green)}
.legend .item.blocked .label{color:var(--coral)}
.legend .item.review .label{color:var(--amber)}
.legend .item.todo .label{color:var(--ink-3)}
.legend .item .val{font-size:20px;font-weight:600;letter-spacing:-.01em}
.legend .right{margin-left:auto;text-align:right}
.legend .right .label{color:var(--ink-2);font-size:12px}
.legend .right .val{color:var(--ink);font-size:20px;font-weight:600}
.bar{
margin-top:14px;height:10px;border-radius:999px;background:#E6E6EA;
display:flex;overflow:hidden;
}
.bar > i{display:block;height:100%}
.bar .seg-done{background:var(--green)}
.bar .seg-blocked{background:var(--coral)}
.bar .seg-review{background:var(--amber)}
.bar .seg-gap{background:#C6C7CC}
.bar .seg-hatch{
background-image:repeating-linear-gradient(135deg,#C6C7CC 0 4px, #D9DADF 4px 8px);
}
.stats{
margin-top:22px;display:grid;grid-template-columns:repeat(5,1fr);gap:14px;
}
.stat{
background:#EAEAEC;border-radius:var(--radius-md);padding:14px 16px;
cursor:pointer;transition:background .15s;
}
.stat:hover{background:#E1E1E4}
.stat .k{font-size:12px;color:var(--ink-2);line-height:1.4}
.stat .v{font-size:22px;font-weight:600;letter-spacing:-.01em;margin-top:10px}
.stat .delta{font-size:11px;color:var(--ink-3);margin-top:2px}
.stat .delta.up{color:var(--green)}
.stat .delta.down{color:var(--coral)}
/* ---------- 工作流条 ---------- */
.strip-title{display:flex;align-items:baseline;gap:10px;margin:28px 4px 14px;}
.strip-title h3{font-size:14px;margin:0;color:var(--ink-2);font-weight:600;letter-spacing:.04em}
.strip-title .muted{color:var(--ink-3);font-size:12px}
.strip-title .more{margin-left:auto;font-size:12px;color:var(--ink-2);cursor:pointer}
.strip-title .more:hover{color:var(--ink)}
.strip{display:grid;grid-template-columns:repeat(4,1fr);gap:16px}
.proj{
background:rgba(255,255,255,.85);
backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);
border-radius:var(--radius-lg);padding:18px;box-shadow:var(--shadow-sm);
position:relative;cursor:pointer;
border:1.5px solid transparent;
transition:box-shadow .15s, border-color .15s, transform .08s;
}
.proj:hover{box-shadow:var(--shadow-md)}
.proj:active{transform:scale(.99)}
.proj.active{border-color:var(--ink);box-shadow:var(--shadow-md);background:#fff}
.proj .pin{display:flex;align-items:center;justify-content:space-between;color:var(--ink-3);font-size:12px}
.proj .pin .num{
width:26px;height:26px;border-radius:50%;display:grid;place-items:center;
background:var(--card-soft);color:var(--ink);font-weight:600;font-size:12px
}
.proj .pin .dots{width:18px;height:18px;display:grid;place-items:center;color:var(--ink-3);cursor:pointer;border-radius:50%}
.proj .pin .dots:hover{background:rgba(15,15,20,.06);color:var(--ink)}
.proj h4{margin:14px 0 4px;font-size:18px;letter-spacing:-.01em}
.proj .date{color:var(--ink-2);font-size:12px}
.proj .row{display:flex;align-items:center;justify-content:space-between;margin-top:18px}
.stack{display:flex;align-items:center}
.stack .avatar{box-shadow:0 0 0 2px #fff;margin-left:-6px}
.stack .avatar:first-child{margin-left:0}
.stack .more{
margin-left:-6px;width:22px;height:22px;border-radius:50%;
background:#E6E6EA;display:grid;place-items:center;font-size:10px;color:var(--ink-2);
box-shadow:0 0 0 2px #fff;font-weight:600;
}
.proj .mini{display:flex;align-items:center;gap:6px;font-size:11px;color:var(--ink-2)}
.proj .mini i{width:6px;height:6px;border-radius:50%}
.proj .mini.g i{background:var(--green)}
.proj .mini.a i{background:var(--amber)}
.proj .mini.c i{background:var(--coral)}
.proj .mini.v i{background:var(--violet)}
/* ---------- 主网格 ---------- */
.grid{display:grid;grid-template-columns:1fr 380px;gap:20px;margin-top:24px}
.panel{
background:rgba(255,255,255,.75);
backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);
border-radius:var(--radius-lg);box-shadow:var(--shadow-sm);
padding:20px 22px;
}
.panel-head{display:flex;align-items:center;gap:12px;margin-bottom:6px}
.panel-head h3{margin:0;font-size:16px;letter-spacing:-.01em}
.panel-head .sub{color:var(--ink-3);font-size:12px}
.panel-head .right{margin-left:auto;display:flex;gap:6px}
.pill{
padding:6px 12px;border-radius:var(--radius-pill);font-size:12px;
color:var(--ink-2);background:rgba(15,15,20,.04);cursor:pointer;
transition:background .15s, color .15s;
}
.pill:hover:not(.on){background:rgba(15,15,20,.08);color:var(--ink)}
.pill.on{background:var(--ink-btn);color:#fff}
/* ---------- 动态时间线 ---------- */
.day{margin-top:18px}
.day-head{display:flex;align-items:center;gap:10px;margin:6px 0 12px;}
.day-head .label{font-size:12px;font-weight:600;letter-spacing:.04em;color:var(--ink-3)}
.day-head .rule{flex:1;height:1px;background:var(--line)}
.event{
display:grid;grid-template-columns:44px 1fr auto;gap:14px;
padding:14px 14px;border-radius:var(--radius-md);
transition:background .15s;cursor:pointer;
}
.event:hover{background:rgba(15,15,20,.025)}
.ev-ava{position:relative;width:32px;height:32px}
.ev-ava .badge{
position:absolute;right:-3px;bottom:-3px;width:18px;height:18px;border-radius:50%;
display:grid;place-items:center;color:#fff;font-size:10px;
box-shadow:0 0 0 2px #fff;
}
.badge.done{background:var(--green)}
.badge.cmt{background:var(--violet)}
.badge.up{background:var(--amber)}
.badge.alert{background:var(--coral)}
.badge.review{background:#15161A}
.ev-body .line{color:var(--ink-2);font-size:13px;line-height:1.6}
.ev-body .line b{color:var(--ink);font-weight:600}
.ev-body .line .tag{
display:inline-flex;align-items:center;gap:5px;padding:2px 8px;border-radius:999px;
background:rgba(15,15,20,.05);color:var(--ink);font-size:12px;font-weight:500;
margin:0 2px;cursor:pointer;
}
.ev-body .line .tag.green{background:var(--green-soft);color:var(--green)}
.ev-body .line .tag.coral{background:var(--coral-soft);color:var(--coral)}
.ev-body .line .tag.amber{background:var(--amber-soft);color:#9A6B17}
.ev-body .line .tag.violet{background:var(--violet-soft);color:var(--violet)}
.ev-body .quote{
margin-top:8px;padding:10px 12px;border-radius:12px;background:var(--card-soft);
color:var(--ink);font-size:13px;line-height:1.65;
}
.ev-body .attach{
margin-top:10px;display:flex;align-items:center;gap:10px;
background:#fff;padding:10px 12px;border-radius:12px;box-shadow:var(--shadow-sm);
max-width:420px;cursor:pointer;transition:transform .08s;
}
.ev-body .attach:hover{box-shadow:var(--shadow-md)}
.ev-body .attach:active{transform:scale(.99)}
.ev-body .attach .ftype{
width:34px;height:34px;border-radius:8px;display:grid;place-items:center;color:#fff;font-size:9px;font-weight:700;letter-spacing:.04em;
}
.ev-body .attach .ftype.pdf{background:#E0341A}
.ev-body .attach .ftype.doc{background:#1B1D24}
.ev-body .attach .ftype.fig{background:#0E7CFF}
.ev-body .attach .meta{font-size:12px;color:var(--ink-3);line-height:1.4}
.ev-body .attach .name{color:var(--ink);font-weight:500;font-size:13px}
.ev-body .review-row{
margin-top:10px;display:flex;gap:8px;align-items:center;
}
.ev-body .approve{
padding:7px 14px;border-radius:999px;background:var(--ink-btn);color:#fff;font-size:12px;font-weight:500;cursor:pointer;
transition:background .15s, transform .08s;
}
.ev-body .approve:hover{background:#000}
.ev-body .approve:active{transform:scale(.96)}
.ev-body .approve.ghost{background:rgba(15,15,20,.05);color:var(--ink)}
.ev-body .approve.ghost:hover{background:rgba(15,15,20,.1)}
.ev-mini-bar{
margin-top:10px;display:flex;align-items:center;gap:10px;max-width:380px;
}
.ev-mini-bar .track{flex:1;height:6px;border-radius:999px;background:#E6E6EA;overflow:hidden}
.ev-mini-bar .fill{display:block;height:100%;background:var(--green)}
.ev-mini-bar .pct{font-size:12px;color:var(--ink-2);min-width:38px;text-align:right}
.ev-time{color:var(--ink-3);font-size:12px;text-align:right;padding-top:2px;white-space:nowrap}
/* ---------- 侧栏 ---------- */
.side{display:flex;flex-direction:column;gap:20px}
.sched-day{display:flex;align-items:center;gap:10px;font-size:12px;color:var(--ink-2);margin-top:6px}
.sched-day .dot{width:6px;height:6px;border-radius:50%;background:var(--green)}
.sched-day .now{color:var(--green);font-weight:600}
.timetable{margin-top:14px;display:flex;flex-direction:column;gap:6px;position:relative}
.slot{display:grid;grid-template-columns:46px 1fr;gap:10px;align-items:flex-start;padding:6px 0;position:relative}
.slot .t{color:var(--ink-3);font-size:11px;font-variant-numeric:tabular-nums;padding-top:4px}
.slot .card{
background:#fff;border-radius:12px;padding:10px 12px;box-shadow:var(--shadow-sm);
display:flex;align-items:center;gap:10px;cursor:pointer;transition:box-shadow .15s, transform .08s;
}
.slot .card:hover{box-shadow:var(--shadow-md)}
.slot .card:active{transform:scale(.99)}
.slot .card .swatch{width:4px;height:32px;border-radius:99px;flex:none;background:var(--violet)}
.slot.green .card .swatch{background:var(--green)}
.slot.amber .card .swatch{background:var(--amber)}
.slot.coral .card .swatch{background:var(--coral)}
.slot .card .title{font-size:13px;font-weight:500}
.slot .card .sub{font-size:11px;color:var(--ink-3);margin-top:2px;line-height:1.5}
.slot.now::before{
content:"";position:absolute;left:38px;right:0;top:50%;height:1px;
background:var(--coral);opacity:.45;
}
.slot.now::after{
content:"";position:absolute;left:34px;top:calc(50% - 3px);width:7px;height:7px;border-radius:50%;background:var(--coral);
}
.slot.empty .card{background:transparent;box-shadow:none;color:var(--ink-3);font-size:12px;padding-left:0;cursor:default}
.slot.empty .card:hover{box-shadow:none}
.roster{margin-top:6px;display:flex;flex-direction:column;}
.roster .person{
display:grid;grid-template-columns:36px 1fr auto;gap:12px;
padding:10px 0;border-top:1px solid var(--line);align-items:center;
cursor:pointer;
}
.roster .person:hover{background:rgba(15,15,20,.02);border-radius:10px;padding-left:6px;padding-right:6px;margin-left:-6px;margin-right:-6px}
.roster .person:first-of-type{border-top:none}
.roster .ava-wrap{position:relative;width:36px;height:36px}
.roster .ava-wrap .avatar{width:36px;height:36px}
.roster .ava-wrap .status{
position:absolute;right:-1px;bottom:-1px;width:11px;height:11px;border-radius:50%;
box-shadow:0 0 0 2px #fff;
}
.status.online{background:var(--green)}
.status.busy{background:var(--coral)}
.status.idle{background:var(--amber)}
.status.off{background:var(--ink-3)}
.roster .name{font-size:13px;font-weight:500}
.roster .role{font-size:11px;color:var(--ink-3);margin-top:2px}
.roster .meta{font-size:11px;color:var(--ink-2);text-align:right;line-height:1.4}
.roster .meta b{display:block;color:var(--ink);font-size:13px;font-weight:600;letter-spacing:-.01em}
/* 其它 */
svg{display:block}
.row{display:flex}
.gap-6{gap:6px}.gap-8{gap:8px}.gap-12{gap:12px}
.mt-12{margin-top:12px}
/* 里程碑:低饱和小色点替代整块上色 */
.ms-list{margin-top:6px;display:flex;flex-direction:column}
.milestone{
display:grid;grid-template-columns:46px 1fr auto;gap:12px;
padding:12px 0;border-top:1px solid var(--line);align-items:center;
cursor:pointer;
}
.milestone:first-child{border-top:none}
.milestone:hover{background:rgba(15,15,20,.02);border-radius:10px;padding-left:6px;padding-right:6px;margin-left:-6px;margin-right:-6px}
.date-tile{
width:46px;padding:5px 0 6px;border-radius:10px;text-align:center;
background:#ECEDF0;color:var(--ink);
}
.date-tile .m{font-size:10px;color:var(--ink-3);letter-spacing:.04em;font-weight:600}
.date-tile .d{font-size:17px;font-weight:600;letter-spacing:-.01em;line-height:1.05;margin-top:1px}
.ms-body .ms-title{font-size:13px;font-weight:500;display:flex;align-items:center;gap:8px}
.ms-body .ms-title .pdot{width:6px;height:6px;border-radius:50%;flex:none}
.ms-body .ms-title .pdot.g{background:var(--green)}
.ms-body .ms-title .pdot.a{background:var(--amber)}
.ms-body .ms-title .pdot.c{background:var(--coral)}
.ms-body .ms-title .pdot.v{background:var(--violet)}
.ms-body .ms-sub{font-size:11px;color:var(--ink-3);margin-top:2px;line-height:1.5}
.ms-meta{font-size:11px;color:var(--ink-2);text-align:right;line-height:1.4}
.ms-meta b{display:block;color:var(--ink);font-size:13px;font-weight:600;letter-spacing:-.01em}
/* 提示条 */
.toast{
position:fixed;left:50%;bottom:32px;transform:translateX(-50%) translateY(20px);
background:var(--ink-btn);color:#fff;padding:10px 16px;border-radius:999px;
font-size:13px;box-shadow:var(--shadow-lg);opacity:0;pointer-events:none;
transition:opacity .2s, transform .2s;z-index:50;
}
.toast.show{opacity:1;transform:translateX(-50%) translateY(0)}
</style>
</head>
<body>
<div class="frame">
<!-- 顶部栏 -->
<header class="topbar">
<div class="brand">
<div class="brand-mark">A</div>
<div class="brand-name">Atelier</div>
</div>
<div class="crumbs">
<span>工作空间</span>
<svg width="10" height="10" viewBox="0 0 10 10"><path d="M3.5 2 6.5 5l-3 3" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span>Q2 工作室</span>
<svg width="10" height="10" viewBox="0 0 10 10"><path d="M3.5 2 6.5 5l-3 3" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span class="now">动态</span>
</div>
<div class="spacer"></div>
<div class="search">
<svg width="14" height="14" viewBox="0 0 14 14"><circle cx="6" cy="6" r="4.2" fill="none" stroke="currentColor" stroke-width="1.4"/><path d="m9.2 9.2 2.4 2.4" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
搜索动态、成员、文件
<span class="kbd">⌘ K</span>
</div>
<div class="iconbtn" title="筛选">
<svg width="16" height="16" viewBox="0 0 16 16"><path d="M2 4h12M4 8h8M6 12h4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>
</div>
<div class="iconbtn" title="通知">
<svg width="16" height="16" viewBox="0 0 16 16"><path d="M4 11V7a4 4 0 1 1 8 0v4l1 1H3l1-1Zm3 2a1.5 1.5 0 0 0 3 0" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/></svg>
<span class="dot"></span>
</div>
<div class="me">
<div class="avatar md" style="background-image:url('https://i.pravatar.cc/120?img=47')"></div>
<div style="font-size:13px;font-weight:500;line-height:1.2">沈宜安
<div style="font-size:11px;color:var(--ink-3);font-weight:400">设计主管</div>
</div>
</div>
</header>
<!-- 二级导航 -->
<div class="subnav">
<div class="tabs">
<div class="tab active">动态 <span class="count">128</span></div>
<div class="tab">日程</div>
<div class="tab">文件</div>
<div class="tab">成员 <span class="count">12</span></div>
<div class="tab">洞察</div>
</div>
<div class="right">
<div class="chip">
<svg width="14" height="14" viewBox="0 0 14 14"><rect x="2" y="3" width="10" height="9" rx="1.4" fill="none" stroke="currentColor" stroke-width="1.4"/><path d="M2 5.5h10M4.5 2v2.2M9.5 2v2.2" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
本周 · 5 月 25 日 – 31 日
<svg width="10" height="10" viewBox="0 0 10 10"><path d="m2.5 4 2.5 2.5L7.5 4" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
</div>
<div class="chip">
全部来源
<svg width="10" height="10" viewBox="0 0 10 10"><path d="m2.5 4 2.5 2.5L7.5 4" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
</div>
<div class="chip solid" data-toast="已创建新里程碑">
<svg width="12" height="12" viewBox="0 0 12 12"><path d="M6 2v8M2 6h8" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>
新建里程碑
</div>
</div>
</div>
<!-- 主进度卡 -->
<section class="hero">
<div class="hero-head">
<div>
<div class="hero-title">第 14 期冲刺 ——「静默地图」进展顺利</div>
<div class="hero-meta">
<span class="play"><svg width="8" height="8" viewBox="0 0 8 8"><path d="M2 1.5v5L7 4z" fill="currentColor"/></svg></span>
<span id="hero-meta-text">5 月 19 日启动 · 剩余 7 天 · 12 位成员</span>
</div>
</div>
<div class="hero-actions">
<div class="btn split" data-toast="正在准备导出…">
<svg width="14" height="14" viewBox="0 0 14 14"><path d="M7 2v6m0 0 2.5-2.5M7 8 4.5 5.5M2.5 11h9" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
导出报告
<span class="caret"><svg width="10" height="10" viewBox="0 0 10 10"><path d="m2.5 4 2.5 2.5L7.5 4" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
</div>
<div class="btn primary" data-toast="冲刺已暂停">
<svg width="12" height="12" viewBox="0 0 12 12"><rect x="3" y="2" width="2" height="8" rx="1" fill="currentColor"/><rect x="7" y="2" width="2" height="8" rx="1" fill="currentColor"/></svg>
暂停冲刺
</div>
</div>
</div>
<div class="hero-progress">
<div class="legend">
<div class="item done">
<div class="label">已交付</div>
<div class="val">52%</div>
</div>
<div class="item blocked">
<div class="label">受阻</div>
<div class="val">6%</div>
</div>
<div class="item review">
<div class="label">评审中</div>
<div class="val">17%</div>
</div>
<div class="item todo">
<div class="label">待办</div>
<div class="val">25%</div>
</div>
<div class="right">
<div class="label">预计完成</div>
<div class="val">6 天 12 时</div>
</div>
</div>
<div class="bar" aria-label="进度条">
<i class="seg-done" style="width:52%"></i>
<i class="seg-blocked" style="width:6%"></i>
<i class="seg-review" style="width:17%"></i>
<i class="seg-gap" style="width:18%"></i>
<i class="seg-hatch" style="width:7%"></i>
</div>
<div class="stats">
<div class="stat">
<div class="k">本周完成<br>任务数</div>
<div class="v">137</div>
<div class="delta up">▲ 较上周 +24</div>
</div>
<div class="stat">
<div class="k">已合并<br>合并请求</div>
<div class="v">42</div>
<div class="delta up">▲ +11</div>
</div>
<div class="stat">
<div class="k">待<br>评审</div>
<div class="v">9</div>
<div class="delta down">3 项待你处理</div>
</div>
<div class="stat">
<div class="k">受阻<br>事项</div>
<div class="v">4</div>
<div class="delta">已提醒 2 位负责人</div>
</div>
<div class="stat">
<div class="k">剩余<br>预算</div>
<div class="v">¥ 18.4 万</div>
<div class="delta">总额 ¥ 46 万</div>
</div>
</div>
</div>
</section>
<!-- 工作流条 -->
<div class="strip-title">
<h3>进行中的工作流</h3>
<span class="muted">显示 4 / 7 个</span>
<span class="more">管理全部工作流 →</span>
</div>
<section class="strip">
<div class="proj active" data-project="quiet-atlas">
<div class="pin">
<span class="num">2</span>
<span class="dots">⋯</span>
</div>
<h4>静默地图 —— Web 端</h4>
<div class="date">里程碑 · 2026 年 5 月 30 日</div>
<div class="row">
<div class="stack">
<div class="avatar sm" style="background-image:url('https://i.pravatar.cc/80?img=47')"></div>
<div class="avatar sm" style="background-image:url('https://i.pravatar.cc/80?img=33')"></div>
<div class="avatar sm" style="background-image:url('https://i.pravatar.cc/80?img=44')"></div>
<div class="avatar sm" style="background-image:url('https://i.pravatar.cc/80?img=49')"></div>
<div class="more">+5</div>
</div>
<div class="mini g"><i></i> 68%</div>
</div>
</div>
<div class="proj" data-project="onboarding">
<div class="pin">
<span class="num">1</span>
<span class="dots">⋯</span>
</div>
<h4>新手引导改版</h4>
<div class="date">启动 · 2026 年 4 月 28 日</div>
<div class="row">
<div class="stack">
<div class="avatar sm" style="background-image:url('https://i.pravatar.cc/80?img=20')"></div>
<div class="avatar sm" style="background-image:url('https://i.pravatar.cc/80?img=23')"></div>
<div class="avatar sm" style="background-image:url('https://i.pravatar.cc/80?img=12')"></div>
</div>
<div class="mini a"><i></i> 42%</div>
</div>
</div>
<div class="proj" data-project="pricing">
<div class="pin">
<span class="num">3</span>
<span class="dots">⋯</span>
</div>
<h4>定价与套餐</h4>
<div class="date">评审 · 2026 年 6 月 4 日</div>
<div class="row">
<div class="stack">
<div class="avatar sm" style="background-image:url('https://i.pravatar.cc/80?img=5')"></div>
<div class="avatar sm" style="background-image:url('https://i.pravatar.cc/80?img=44')"></div>
</div>
<div class="mini c"><i></i> 受阻</div>
</div>
</div>
<div class="proj" data-project="brand-voice">
<div class="pin">
<span class="num">4</span>
<span class="dots">⋯</span>
</div>
<h4>品牌语调 v2</h4>
<div class="date">调研 · 2026 年 6 月 10 日</div>
<div class="row">
<div class="stack">
<div class="avatar sm" style="background-image:url('https://i.pravatar.cc/80?img=33')"></div>
<div class="avatar sm" style="background-image:url('https://i.pravatar.cc/80?img=15')"></div>
<div class="avatar sm" style="background-image:url('https://i.pravatar.cc/80?img=20')"></div>
<div class="more">+2</div>
</div>
<div class="mini v"><i></i> 18%</div>
</div>
</div>
</section>
<!-- 主网格 -->
<div class="grid">
<!-- 动态流 -->
<section class="panel">
<div class="panel-head">
<h3>动态流</h3>
<span class="sub">本周 128 条更新</span>
<div class="right">
<div class="pill on">全部</div>
<div class="pill">@ 我的</div>
<div class="pill">评审</div>
<div class="pill">文件</div>
</div>
</div>
<!-- 今天 -->
<div class="day">
<div class="day-head"><span class="label">今天 · 5 月 28 日 周四</span><span class="rule"></span></div>
<div class="event">
<div class="ev-ava">
<div class="avatar md" style="background-image:url('https://i.pravatar.cc/120?img=33')"></div>
<div class="badge done"><svg width="9" height="9" viewBox="0 0 9 9"><path d="M2 5l1.8 1.8L7 3" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg></div>
</div>
<div class="ev-body">
<div class="line"><b>陈彦霖</b> 交付了 <span class="tag green">QA-218 · 收件箱空状态</span>,归属 <b>静默地图 —— Web 端</b></div>
<div class="ev-mini-bar">
<span class="track"><span class="fill" style="width:100%"></span></span>
<span class="pct">100%</span>
</div>
</div>
<div class="ev-time">2 分钟前</div>
</div>
<div class="event">
<div class="ev-ava">
<div class="avatar md" style="background-image:url('https://i.pravatar.cc/120?img=49')"></div>
<div class="badge alert"><svg width="9" height="9" viewBox="0 0 9 9"><path d="M4.5 2v3M4.5 6.5v.4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg></div>
</div>
<div class="ev-body">
<div class="line"><b>黎曼雅</b> 将 <span class="tag coral">PR-441 · 登录限流</span> 标记为受阻 —— 等待基础设施密钥轮换。</div>
<div class="quote">「平台组今天能否对接一下?新密钥不到位之前,限流策略没法上线。」</div>
<div class="review-row">
<div class="approve" data-toast="已转派给平台组">转派给平台组</div>
<div class="approve ghost" data-toast="已暂缓 24 小时">暂缓 24 小时</div>
</div>
</div>
<div class="ev-time">18 分钟前</div>
</div>
<div class="event">
<div class="ev-ava">
<div class="avatar md" style="background-image:url('https://i.pravatar.cc/120?img=47')"></div>
<div class="badge cmt"><svg width="9" height="9" viewBox="0 0 9 9"><path d="M2 3.5h5v2.5H4.5L3 7.5V6H2V3.5Z" fill="currentColor"/></svg></div>
</div>
<div class="ev-body">
<div class="line"><b>你</b> 在 <span class="tag">定价页 · Hero 版式</span> 留言 · 3 条回复</div>
<div class="quote">「B 版在 1280 下更清爽,但移动端 CTA 对比度掉得有点厉害 —— 锁版前先把 C 版的深色 CTA 测一下。」</div>
</div>
<div class="ev-time">10:42</div>
</div>
<div class="event">
<div class="ev-ava">
<div class="avatar md" style="background-image:url('https://i.pravatar.cc/120?img=44')"></div>
<div class="badge up"><svg width="9" height="9" viewBox="0 0 9 9"><path d="M4.5 2v4.5M2.5 4 4.5 2l2 2" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg></div>
</div>
<div class="ev-body">
<div class="line"><b>朴雅然</b> 上传了一份新版本待评审</div>
<div class="attach" data-toast="正在打开文件…">
<div class="ftype pdf">PDF</div>
<div>
<div class="name">静默地图-用户调研复盘.pdf</div>
<div class="meta">2.4 MB · 18 页 · 关联到 <b style="color:var(--ink)">前期调研</b></div>
</div>
</div>
<div class="attach" style="margin-top:6px" data-toast="正在打开 Figma…">
<div class="ftype fig">FIG</div>
<div>
<div class="name">新手引导 02 —— 首登流程</div>
<div class="meta">已更新 11 帧 · 4 条评论</div>
</div>
</div>
</div>
<div class="ev-time">09:55</div>
</div>
<div class="event">
<div class="ev-ava">
<div class="avatar md" style="background-image:url('https://i.pravatar.cc/120?img=20')"></div>
<div class="badge review"><svg width="9" height="9" viewBox="0 0 9 9"><circle cx="4.5" cy="4.5" r="2.3" fill="none" stroke="currentColor" stroke-width="1.4"/><path d="M3.6 4.5h1.8" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg></div>
</div>
<div class="ev-body">
<div class="line"><b>陶涵</b> 邀请你评审 <span class="tag violet">品牌语调 v2 · 语气光谱</span></div>
<div class="review-row">
<div class="approve" data-toast="正在打开评审…">开始评审</div>
<div class="approve ghost" data-toast="已拒绝评审">拒绝</div>
<span style="color:var(--ink-3);font-size:12px">截止 周五 17:00</span>
</div>
</div>
<div class="ev-time">09:12</div>
</div>
</div>
<!-- 昨天 -->
<div class="day">
<div class="day-head"><span class="label">昨天 · 5 月 27 日 周三</span><span class="rule"></span></div>
<div class="event">
<div class="ev-ava">
<div class="avatar md" style="background-image:url('https://i.pravatar.cc/120?img=33')"></div>
<div class="badge done"><svg width="9" height="9" viewBox="0 0 9 9"><path d="M2 5l1.8 1.8L7 3" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg></div>
</div>
<div class="ev-body">
<div class="line"><b>陈彦霖</b> 关闭了里程碑 <span class="tag green">M-08 · API 接口冻结</span> —— 14 / 14 张工单全部交付。</div>
<div class="ev-mini-bar">
<span class="track"><span class="fill" style="width:100%"></span></span>
<span class="pct">14/14</span>
</div>
</div>
<div class="ev-time">18:04</div>
</div>
<div class="event">
<div class="ev-ava">
<div class="avatar md" style="background-image:url('https://i.pravatar.cc/120?img=23')"></div>
<div class="badge cmt"><svg width="9" height="9" viewBox="0 0 9 9"><path d="M2 3.5h5v2.5H4.5L3 7.5V6H2V3.5Z" fill="currentColor"/></svg></div>
</div>
<div class="ev-body">
<div class="line"><b>权丽</b> 把 6 张工单从 <span class="tag amber">评审中</span> 推进到 <span class="tag green">已交付</span>,归属 <b>新手引导改版</b></div>
</div>
<div class="ev-time">16:30</div>
</div>
<div class="event">
<div class="ev-ava">
<div class="avatar md" style="background-image:url('https://i.pravatar.cc/120?img=12')"></div>
<div class="badge up"><svg width="9" height="9" viewBox="0 0 9 9"><path d="M4.5 2v4.5M2.5 4 4.5 2l2 2" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg></div>
</div>
<div class="ev-body">
<div class="line"><b>顾维</b> 分享了会议纪要 —— 第 14 期冲刺中期检视</div>
<div class="attach" data-toast="正在打开文档…">
<div class="ftype doc">DOC</div>
<div>
<div class="name">冲刺中期复盘 · 5 月 27 日.docx</div>
<div class="meta">0.6 MB · 4 个章节 · 8 项行动事项</div>
</div>
</div>
</div>
<div class="ev-time">14:12</div>
</div>
<div class="event">
<div class="ev-ava">
<div class="avatar md" style="background-image:url('https://i.pravatar.cc/120?img=15')"></div>
<div class="badge cmt"><svg width="9" height="9" viewBox="0 0 9 9"><path d="M2 3.5h5v2.5H4.5L3 7.5V6H2V3.5Z" fill="currentColor"/></svg></div>
</div>
<div class="ev-body">
<div class="line"><b>谢有福</b> 在 <span class="tag">品牌语调 v2 · 示例卡组</span> 提了一个问题</div>
<div class="quote">「『俏皮但精准』这一行 —— 我们现在还用『鳄梨』那段文案做参考,还是 Q1 评审之后就退役了?」</div>
</div>
<div class="ev-time">11:48</div>
</div>
</div>
<!-- 周二 -->
<div class="day">
<div class="day-head"><span class="label">5 月 26 日 周二</span><span class="rule"></span></div>
<div class="event">
<div class="ev-ava">
<div class="avatar md" style="background-image:url('https://i.pravatar.cc/120?img=5')"></div>
<div class="badge alert"><svg width="9" height="9" viewBox="0 0 9 9"><path d="M4.5 2v3M4.5 6.5v.4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg></div>
</div>
<div class="ev-body">
<div class="line"><b>艾达</b> 在 <span class="tag coral">定价与套餐</span> 上报了一项风险 —— 欧盟披露文案仍在等待法务复审。</div>
<div class="ev-mini-bar">
<span class="track"><span class="fill" style="width:34%;background:var(--coral)"></span></span>
<span class="pct">34%</span>
</div>
</div>
<div class="ev-time">17:22</div>
</div>
<div class="event">
<div class="ev-ava">
<div class="avatar md" style="background-image:url('https://i.pravatar.cc/120?img=20')"></div>
<div class="badge done"><svg width="9" height="9" viewBox="0 0 9 9"><path d="M2 5l1.8 1.8L7 3" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg></div>
</div>
<div class="ev-body">
<div class="line"><b>陶涵</b> 完成了调研访谈 —— 9 场访谈,共计 14 小时素材。</div>
</div>
<div class="ev-time">10:01</div>
</div>
</div>
<div style="text-align:center;margin-top:14px">
<span class="pill" id="load-more">加载更早的动态</span>
</div>
</section>
<!-- 侧栏 -->
<aside class="side">
<!-- 今日日程 -->
<section class="panel">
<div class="panel-head">
<h3>今日日程</h3>
<div class="right"><div class="pill on">日视图</div><div class="pill">周视图</div></div>
</div>
<div class="sched-day">
<span class="dot"></span><span class="now">现在 · 11:38</span>
<span style="margin-left:auto">3 场会议 · 2 段专注时段</span>
</div>
<div class="timetable">
<div class="slot green">
<div class="t">09:00</div>
<div class="card">
<span class="swatch"></span>
<div>
<div class="title">每日站会</div>
<div class="sub">研发组 · 15 分钟 · 8 人参与</div>
</div>
</div>
</div>
<div class="slot">
<div class="t">10:00</div>
<div class="card">
<span class="swatch"></span>
<div>
<div class="title">静默地图 —— 设计评审</div>
<div class="sub">与陈彦霖、朴雅然、黎曼雅 · 45 分钟</div>
</div>
</div>
</div>
<div class="slot amber now">
<div class="t">11:30</div>
<div class="card">
<span class="swatch"></span>
<div>
<div class="title">专注时段 —— 引导文案打磨</div>
<div class="sub">结束于 13:00 · 通知已静音</div>
</div>
</div>
</div>
<div class="slot">
<div class="t">13:30</div>
<div class="card">
<span class="swatch"></span>
<div>
<div class="title">与陶涵 1:1</div>
<div class="sub">语气光谱走查 · 30 分钟</div>
</div>
</div>
</div>
<div class="slot coral">
<div class="t">15:00</div>
<div class="card">
<span class="swatch"></span>
<div>
<div class="title">定价方案疏通会</div>
<div class="sub">与艾达、法务 · 45 分钟 · 包豪斯会议室</div>
</div>
</div>
</div>
<div class="slot">
<div class="t">16:30</div>
<div class="card">
<span class="swatch"></span>
<div>
<div class="title">冲刺评审准备</div>
<div class="sub">独立工作 · 60 分钟</div>
</div>
</div>
</div>
<div class="slot empty">
<div class="t">18:00</div>
<div class="card">今日结束 —— 2 项未完成事项将顺延到明天。</div>
</div>
</div>
</section>
<!-- 团队状态 -->
<section class="panel">
<div class="panel-head">
<h3>本周团队</h3>
<span class="sub">共 12 人</span>
<div class="right"><div class="pill on">在线</div><div class="pill">全部</div></div>
</div>
<div class="roster">
<div class="person">
<div class="ava-wrap">
<div class="avatar" style="background-image:url('https://i.pravatar.cc/120?img=33')"></div>
<span class="status online"></span>
</div>
<div>
<div class="name">陈彦霖</div>
<div class="role">研发 · 静默地图</div>
</div>
<div class="meta"><b>18 小时</b>已记录</div>
</div>
<div class="person">
<div class="ava-wrap">
<div class="avatar" style="background-image:url('https://i.pravatar.cc/120?img=49')"></div>
<span class="status busy"></span>
</div>
<div>
<div class="name">黎曼雅</div>
<div class="role">平台 · 会议中</div>
</div>
<div class="meta"><b>14 小时</b>已记录</div>
</div>
<div class="person">
<div class="ava-wrap">
<div class="avatar" style="background-image:url('https://i.pravatar.cc/120?img=44')"></div>
<span class="status online"></span>
</div>
<div>
<div class="name">朴雅然</div>
<div class="role">用户研究</div>
</div>
<div class="meta"><b>11 小时</b>已记录</div>
</div>
<div class="person">
<div class="ava-wrap">
<div class="avatar" style="background-image:url('https://i.pravatar.cc/120?img=20')"></div>
<span class="status idle"></span>
</div>
<div>
<div class="name">陶涵</div>
<div class="role">品牌 · 专注时段</div>
</div>
<div class="meta"><b>9 小时</b>已记录</div>
</div>
<div class="person">
<div class="ava-wrap">
<div class="avatar" style="background-image:url('https://i.pravatar.cc/120?img=23')"></div>
<span class="status online"></span>
</div>
<div>
<div class="name">权丽</div>
<div class="role">新手引导 · 产品</div>
</div>
<div class="meta"><b>16 小时</b>已记录</div>
</div>
<div class="person">
<div class="ava-wrap">
<div class="avatar" style="background-image:url('https://i.pravatar.cc/120?img=12')"></div>
<span class="status off"></span>
</div>
<div>
<div class="name">顾维</div>
<div class="role">休假 · 下周一回归</div>
</div>
<div class="meta"><b>—</b>休假中</div>
</div>
</div>
</section>
<!-- 即将到来的里程碑 -->
<section class="panel">
<div class="panel-head">
<h3>即将到来的里程碑</h3>
<span class="sub" id="ms-sub">未来 14 天</span>
</div>
<div class="ms-list" id="ms-list"></div>
</section>
</aside>
</div>
</div>
<div class="toast" id="toast">操作完成</div>
<script>
// ---------- 工作流数据 ----------
const projects = {
'quiet-atlas': {
label: '静默地图',
title: '第 14 期冲刺 ——「静默地图」进展顺利',
meta: '5 月 19 日启动 · 剩余 7 天 · 12 位成员',
legend: ['52%','6%','17%','25%','6 天 12 时'],
bar: [52, 6, 17, 18, 7],
stats: [
['本周完成<br>任务数','137','▲ 较上周 +24','up'],
['已合并<br>合并请求','42','▲ +11','up'],
['待<br>评审','9','3 项待你处理','down'],
['受阻<br>事项','4','已提醒 2 位负责人',''],
['剩余<br>预算','¥ 18.4 万','总额 ¥ 46 万','']
]
},
'onboarding': {
label: '新手引导',
title: '新手引导改版 —— 首登流程评审中',
meta: '4 月 28 日启动 · 已推进 30 天 · 6 位成员',
legend: ['38%','0%','22%','40%','12 天 4 时'],
bar: [38, 0, 22, 30, 10],
stats: [
['本周完成<br>任务数','64','▲ 较上周 +9','up'],
['已合并<br>合并请求','18','▲ +4','up'],
['待<br>评审','5','2 项待陶涵处理',''],
['受阻<br>事项','0','暂无阻塞','up'],
['剩余<br>预算','¥ 9.2 万','总额 ¥ 18 万','']
]
},
'pricing': {
label: '定价方案',
title: '定价与套餐 —— 法务评审受阻',
meta: '6 月 4 日评审 · 等待欧盟披露文案放行',
legend: ['71%','18%','8%','3%','暂停推进'],
bar: [71, 18, 8, 2, 1],
stats: [
['本周完成<br>任务数','12','▼ 较上周 −8','down'],
['已合并<br>合并请求','5','— 与上周持平',''],
['待<br>评审','2','法务复审中','down'],
['受阻<br>事项','3','全部卡在法务',''],
['剩余<br>预算','¥ 4.1 万','总额 ¥ 12 万','']
]
},
'brand-voice': {
label: '品牌语调',
title: '品牌语调 v2 —— 调研阶段',
meta: '调研 · 完成 9 场访谈 · 6 位成员',
legend: ['18%','0%','12%','70%','24 天 8 时'],
bar: [18, 0, 12, 50, 20],
stats: [
['本周完成<br>任务数','23','▲ 较上周 +11','up'],
['已合并<br>合并请求','3','调研产出',''],
['待<br>评审','4','1 项待你处理','down'],
['受阻<br>事项','1','语气基准存争议',''],
['剩余<br>预算','¥ 14.7 万','总额 ¥ 22 万','']
]
}
};
const milestones = {
'quiet-atlas': [
{d:'30', m:'5 月', dot:'g', title:'静默地图 —— Web 端封板', sub:'周六 · 代码冻结,main 分支停止合并', delta:'+2 天'},
{d:'05', m:'6 月', dot:'g', title:'切出预发环境给 QA', sub:'周五 · 回归测试启动', delta:'+8 天'},
{d:'12', m:'6 月', dot:'g', title:'对外发布', sub:'周五 · 09:00 灰度窗口', delta:'+15 天'}
],
'onboarding': [
{d:'02', m:'6 月', dot:'a', title:'首登流程可用性测试', sub:'周一 · 已预约 6 场', delta:'+5 天'},
{d:'06', m:'6 月', dot:'a', title:'文案定稿', sub:'周五 · 文案冻结进入翻译', delta:'+9 天'},
{d:'09', m:'6 月', dot:'a', title:'第二期启动', sub:'周一 · 与产品过范围', delta:'+12 天'}
],
'pricing': [
{d:'04', m:'6 月', dot:'c', title:'定价 —— 上线评审', sub:'周四 · 与管理层及法务对齐', delta:'+7 天'},
{d:'07', m:'6 月', dot:'c', title:'法务放行截止', sub:'周日 · 欧盟披露文案', delta:'+10 天'},
{d:'11', m:'6 月', dot:'c', title:'定价方案上线', sub:'周四 · 官网与 API 同步发布', delta:'+14 天'}
],
'brand-voice': [
{d:'10', m:'6 月', dot:'v', title:'语调中期检视', sub:'周三 · 向团队共享读出', delta:'+13 天'},
{d:'16', m:'6 月', dot:'v', title:'示例卡组 v1', sub:'周二 · 跨团队评论一轮', delta:'+19 天'},
{d:'18', m:'6 月', dot:'v', title:'语调指南 v2 上线', sub:'周四 · 收录到团队手册', delta:'+21 天'}
]
};
function renderMilestones(key){
const list = milestones[key] || [];
const html = list.map(m => `
<div class="milestone" data-toast="正在打开 ${m.title}">
<div class="date-tile">
<div class="m">${m.m}</div>
<div class="d">${m.d}</div>
</div>
<div class="ms-body">
<div class="ms-title"><span class="pdot ${m.dot}"></span>${m.title}</div>
<div class="ms-sub">${m.sub}</div>
</div>
<div class="ms-meta"><b>${m.delta}</b>${list.indexOf(m) === 0 ? '下一个' : '已规划'}</div>
</div>
`).join('');
document.getElementById('ms-list').innerHTML = html;
document.getElementById('ms-sub').textContent = `已规划 ${list.length} 个 · 未来 21 天`;
document.querySelectorAll('#ms-list [data-toast]').forEach(el => {
el.addEventListener('click', e => { e.stopPropagation(); toast(el.dataset.toast); });
});
}
function applyProject(key){
const p = projects[key];
if (!p) return;
// 主进度卡头部
document.querySelector('.hero-title').textContent = p.title;
document.getElementById('hero-meta-text').textContent = p.meta;
// 图例
const legendVals = document.querySelectorAll('.legend .item .val');
[0,1,2,3].forEach(i => legendVals[i].textContent = p.legend[i]);
document.querySelector('.legend .right .val').textContent = p.legend[4];
// 进度条
const segs = document.querySelectorAll('.bar > i');
segs.forEach((s, i) => s.style.width = p.bar[i] + '%');
// 指标
document.querySelectorAll('.stat').forEach((s, i) => {
const data = p.stats[i];
s.querySelector('.k').innerHTML = data[0];
s.querySelector('.v').textContent = data[1];
const delta = s.querySelector('.delta');
delta.textContent = data[2];
delta.className = 'delta' + (data[3] ? ' ' + data[3] : '');
});
// 里程碑
renderMilestones(key);
toast('已切换至 ' + p.label);
}
// 提示条
const toastEl = document.getElementById('toast');
let toastTimer;
function toast(msg){
toastEl.textContent = msg;
toastEl.classList.add('show');
clearTimeout(toastTimer);
toastTimer = setTimeout(()=>toastEl.classList.remove('show'), 1800);
}
// Tab 切换
document.querySelectorAll('.tabs').forEach(group => {
group.addEventListener('click', e => {
const t = e.target.closest('.tab');
if (!t) return;
group.querySelectorAll('.tab').forEach(x => x.classList.remove('active'));
t.classList.add('active');
});
});
// Pill 切换
document.querySelectorAll('.panel-head .right').forEach(group => {
group.addEventListener('click', e => {
const p = e.target.closest('.pill');
if (!p) return;
group.querySelectorAll('.pill').forEach(x => x.classList.remove('on'));
p.classList.add('on');
});
});
// 工作流卡选中
const stripEl = document.querySelector('.strip');
if (stripEl) {
stripEl.addEventListener('click', e => {
if (e.target.closest('.dots')) { e.stopPropagation(); toast('工作流选项'); return; }
const card = e.target.closest('.proj');
if (!card) return;
stripEl.querySelectorAll('.proj').forEach(x => x.classList.remove('active'));
card.classList.add('active');
if (card.dataset.project) applyProject(card.dataset.project);
});
}
// 初始渲染
const initial = document.querySelector('.proj.active');
if (initial && initial.dataset.project) {
renderMilestones(initial.dataset.project);
} else {
renderMilestones('quiet-atlas');
}
// 通用 toast 绑定
document.querySelectorAll('[data-toast]').forEach(el => {
el.addEventListener('click', e => {
e.stopPropagation();
toast(el.dataset.toast);
});
});
// 加载更多
const loadBtn = document.getElementById('load-more');
if (loadBtn) loadBtn.addEventListener('click', () => toast('正在加载更早的动态…'));
// 面包屑与品牌
document.querySelectorAll('.crumbs span, .brand').forEach(el => {
el.addEventListener('click', () => toast('跳转至 ' + (el.textContent.trim() || '首页')));
});
// 顶部图标按钮
document.querySelectorAll('.iconbtn').forEach((el, i) => {
el.addEventListener('click', () => toast(i === 0 ? '已打开筛选' : '已打开通知'));
});
document.querySelector('.me')?.addEventListener('click', () => toast('个人菜单'));
document.querySelector('.search')?.addEventListener('click', () => toast('搜索 ⌘ K'));
// 动态条目点击
document.querySelectorAll('.event').forEach(ev => {
ev.addEventListener('click', e => {
if (e.target.closest('[data-toast], .tag, .attach, .approve')) return;
toast('正在打开动态详情');
});
});
// 日程
document.querySelectorAll('.timetable .slot:not(.empty) .card').forEach(c => {
c.addEventListener('click', () => toast('正在打开日程详情'));
});
// 团队成员
document.querySelectorAll('.roster .person').forEach(p => {
p.addEventListener('click', () => {
const name = p.querySelector('.name')?.textContent || '成员';
toast('正在打开 ' + name);
});
});
// 事件内的标签
document.querySelectorAll('.ev-body .tag').forEach(t => {
t.addEventListener('click', e => {
e.stopPropagation();
toast('按 ' + t.textContent.trim() + ' 筛选');
});
});
// 指标卡
document.querySelectorAll('.stat').forEach(s => {
s.addEventListener('click', () => {
const k = s.querySelector('.k')?.textContent.replace(/\n/g, ' ').trim();
toast('下钻查看:' + k);
});
});
// 冲刺计时
document.querySelector('.hero-meta .play')?.addEventListener('click', e => {
e.stopPropagation();
toast('冲刺计时');
});
</script>
</body>
</html>