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
1293 lines
55 KiB
HTML
Executable File
1293 lines
55 KiB
HTML
Executable File
<!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>
|