|
|
@@ -98,17 +98,28 @@ export class ViewWorkbench extends LitElement {
|
|
|
.stg .b.blue { background: var(--kf-s1); } .stg .b.violet { background: var(--kf-s2); } .stg .b.teal { background: var(--kf-s3); }
|
|
|
.stg .b.amber { background: var(--kf-s4); } .stg .b.rose { background: var(--kf-s5); }
|
|
|
.stg .n { justify-self: end; font-size: 20px; font-weight: 600; line-height: 1; letter-spacing: var(--ls-cn-display); color: var(--gray-12); font-variant-numeric: tabular-nums; }
|
|
|
+ .stg .sf { grid-column: 1 / -1; font-size: 12px; line-height: 1.3; color: var(--kf-ink-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
|
.stg .f { grid-column: 1 / -1; height: 4px; border-radius: 2px; background: rgba(16, 24, 40, 0.07); overflow: hidden; margin-top: 4px; }
|
|
|
.stg .f i { display: block; height: 100%; border-radius: 2px; background: currentColor; width: calc(var(--v) * 100%); }
|
|
|
.stg.blue { color: var(--kf-s1); } .stg.violet { color: var(--kf-s2); } .stg.teal { color: var(--kf-s3); } .stg.amber { color: var(--kf-s4); } .stg.rose { color: var(--kf-s5); }
|
|
|
.stg.todo { color: var(--kf-ink-3); background: transparent; border-style: dashed; border-color: color-mix(in srgb, var(--kf-ink-3) 22%, transparent); }
|
|
|
.stg.todo .n { color: var(--kf-ink-3); font-weight: 500; }
|
|
|
- /* 待评分:只有这几行时 A 卡会矮一截,min-height 让 A 在有卡住的人时仍撑得起来 */
|
|
|
- .pending { display: grid; gap: 0; }
|
|
|
+ /* A 卡下半:待评分(左)| 卡池进度(右)。两个分区头共用 .lbl,行底对齐 12 */
|
|
|
+ .me-split { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); gap: 32px; }
|
|
|
+ .me-split > div { min-width: 0; }
|
|
|
+ .me-split .lbl { min-height: 22px; }
|
|
|
+ .pending { display: grid; gap: 0; margin-top: 4px; }
|
|
|
.pending li { display: flex; align-items: baseline; gap: 8px; min-height: 32px; font-size: 14px; }
|
|
|
.pending li a { color: var(--kf-ink); text-decoration: none; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
|
.pending li a:hover { color: var(--kf-accent-ink); }
|
|
|
.pending li .s { margin-left: auto; font-weight: 600; color: var(--kf-accent-ink); font-variant-numeric: tabular-nums; }
|
|
|
+ .me-none { font-size: 14px; color: var(--kf-ink-3); padding-top: 10px; }
|
|
|
+ .prog { display: grid; gap: 0; margin-top: 4px; }
|
|
|
+ .prog li { display: grid; grid-template-columns: 6px auto 1fr auto; align-items: baseline; gap: 10px; min-height: 32px; font-size: 14px; }
|
|
|
+ .prog .pk { color: var(--kf-ink-2); }
|
|
|
+ .prog .pv { color: var(--kf-ink); font-variant-numeric: tabular-nums; }
|
|
|
+ .prog .pv b { font-weight: 600; }
|
|
|
+ .prog .pp { color: var(--kf-ink-3); font-size: 12px; font-variant-numeric: tabular-nums; }
|
|
|
|
|
|
/* ---------- B 卡:现场(深色) ---------- */
|
|
|
.live { display: grid; gap: var(--kf-space-in); height: 100%; align-content: start;
|
|
|
@@ -142,16 +153,21 @@ export class ViewWorkbench extends LitElement {
|
|
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-height: 18px; }
|
|
|
|
|
|
/* ---------- F:最近动过(通栏) ---------- */
|
|
|
- .feed { display: grid; gap: 0; }
|
|
|
- .feed li { display: flex; align-items: baseline; gap: 12px; min-height: 44px; padding: 10px 0; border-top: 1px solid var(--kf-line); font-size: 14px; }
|
|
|
- .feed li:first-child { border-top: 0; padding-top: 0; }
|
|
|
- .feed li a { color: var(--kf-ink); font-weight: 500; text-decoration: none; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
|
- .feed li a:hover { color: var(--kf-accent-ink); }
|
|
|
- .feed .st { font-size: 12px; color: var(--kf-ink-3); flex: none; }
|
|
|
- .feed .tm { margin-left: auto; font-size: 12px; color: var(--kf-ink-3); font-variant-numeric: tabular-nums; flex: none; }
|
|
|
- .feed-head { display: flex; align-items: center; gap: 8px; }
|
|
|
+ /* 分区标签:三个区块共用同一种「12px 灰小标 + 右侧箭头」,整页只有一种小标题样式 */
|
|
|
+ .feed-head { display: flex; align-items: center; gap: 8px; min-height: 22px; }
|
|
|
.feed-head .go { margin-left: auto; }
|
|
|
- .feed-empty { font-size: 14px; color: var(--kf-ink-3); padding: 8px 0; }
|
|
|
+ /* 行:左标题 / 中阶段 / 右时间。网格而不是 flex —— 中间那列 auto 宽,右边永远贴右 */
|
|
|
+ .feed { display: grid; margin-top: 8px; }
|
|
|
+ .feed li { display: grid; grid-template-columns: minmax(0, 1fr) auto 64px; align-items: baseline; gap: 16px; padding: 11px 0; border-top: 1px solid var(--kf-line); font-size: 14px; }
|
|
|
+ .feed li:first-child { border-top: 0; padding-top: 4px; }
|
|
|
+ .feed li:last-child { padding-bottom: 4px; border-bottom: 0; }
|
|
|
+ .feed li:last-child a { font-weight: 500; }
|
|
|
+ .feed-more { display: inline-flex; align-items: center; gap: 4px; margin-top: 6px; font-size: 14px; }
|
|
|
+ .feed li a { color: var(--kf-ink); font-weight: 500; text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
|
+ .feed li a:hover { color: var(--kf-accent-ink); }
|
|
|
+ .feed .st { font-size: 12px; color: var(--kf-ink-3); white-space: nowrap; }
|
|
|
+ .feed .tm { font-size: 12px; color: var(--kf-ink-3); font-variant-numeric: tabular-nums; text-align: right; white-space: nowrap; }
|
|
|
+ .feed-empty { font-size: 14px; color: var(--kf-ink-3); padding: 8px 0 0; }
|
|
|
.foot-links { display: none; }
|
|
|
|
|
|
/* 平板:A/B 变 7/5 太挤,直接上下堆;C/D/E 保持一行 */
|
|
|
@@ -161,24 +177,39 @@ export class ViewWorkbench extends LitElement {
|
|
|
}
|
|
|
/* 手机 393:A/B 竖排,C/D/E 三卡一行(每卡 3 行文字仍放得下),可点区域 44px */
|
|
|
@media (max-width: 767px) {
|
|
|
- .bento { gap: var(--kf-space-card); margin-top: var(--kf-space-card); }
|
|
|
+ .bento { gap: var(--kf-space-card); margin-top: 24px; }
|
|
|
+ .p-head + .bento { margin-top: 24px; }
|
|
|
.bento > .row { gap: var(--kf-space-card); }
|
|
|
- .bento > .row.cde { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
|
|
- .stagebar { grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 6px; }
|
|
|
- .stg { padding: 8px 6px; grid-template-columns: auto; gap: 4px; justify-items: start; }
|
|
|
- .stg .n { justify-self: start; font-size: 16px; }
|
|
|
- .me-nums { margin-left: 0; width: 100%; justify-content: space-between; gap: 12px; }
|
|
|
- .me-nums div { text-align: left; }
|
|
|
- .stat b { font-size: 24px; }
|
|
|
+ .bento > .row.cde { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
|
|
|
+ .me { gap: 12px; }
|
|
|
+ .me-id h3, .me-id .sub { display: inline; }
|
|
|
+ .me-id > div { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
|
|
|
+ .me-nums { margin-left: 0; width: 100%; justify-content: flex-start; gap: 24px; }
|
|
|
+ .me-nums div { text-align: left; display: flex; align-items: baseline; gap: 4px; }
|
|
|
+ /* 五阶段一行放不下英文名:两行三列(第五格占满第二行左半),英文名折行两行后省略 */
|
|
|
+ .stagebar { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
|
|
|
+ .stg { padding: 10px; }
|
|
|
+ .stg .sf { white-space: normal; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; line-height: 1.35; }
|
|
|
+ .me-split { grid-template-columns: minmax(0, 1fr); gap: 12px; }
|
|
|
+ .prog li { min-height: 32px; }
|
|
|
+ .stat { gap: 6px; }
|
|
|
+ .stat b { font-size: 20px; }
|
|
|
+ .stat b .u { font-size: 12px; }
|
|
|
+ .stat .top { min-height: 18px; }
|
|
|
.stat .top .go { display: none; }
|
|
|
+ /* 手机上一律 14:说明文字在 393 宽下 12px 读不清;只是「文字」不换档,数字仍按档位 */
|
|
|
+ .me-none, .empty { font-size: 14px; }
|
|
|
.btn-key { display: none; }
|
|
|
- .feed li { flex-wrap: wrap; }
|
|
|
- .feed li a { white-space: normal; flex: 1 1 100%; }
|
|
|
+ /* F:两行 —— 标题占满第一行,作者 / 时间在第二行两端 */
|
|
|
+ .feed li { grid-template-columns: minmax(0, 1fr) auto; row-gap: 2px; }
|
|
|
+ .feed li a { grid-column: 1 / -1; white-space: normal; }
|
|
|
.go, .hd .go, .me-id .go { min-height: 44px; min-width: 44px; justify-content: center; }
|
|
|
+ .feed-more { min-height: 44px; align-items: center; }
|
|
|
.pending li { min-height: 44px; }
|
|
|
+ .empty { font-size: 14px; }
|
|
|
.foot-links { display: flex; gap: 8px; margin-top: var(--kf-space-card); }
|
|
|
.foot-links a { display: inline-flex; align-items: center; gap: 6px; min-height: 44px; padding: 0 14px; border-radius: var(--r-md); font-size: 14px; font-weight: 600;
|
|
|
- color: var(--kf-accent-ink); text-decoration: none; background: #fff; border: 1px solid rgba(0, 0, 0, 0.06); }
|
|
|
+ color: var(--kf-accent-ink); text-decoration: none; background: var(--kf-surface); border: 1px solid var(--kf-line-soft); }
|
|
|
}
|
|
|
`];
|
|
|
|
|
|
@@ -192,7 +223,7 @@ export class ViewWorkbench extends LitElement {
|
|
|
return html`
|
|
|
${head('我的推进', '五阶段进度与现场状态', html`<a class="go" href=${href('/pool')} data-link>机会卡池 →</a>`)}
|
|
|
<div class="bento">
|
|
|
- <div class="row ab kf-enter" style="--i:0">
|
|
|
+ <div class="row ab grid2 kf-enter" style="--i:0">
|
|
|
<kf-glass-card data-role="a">${this.#mine(me, pool, live)}</kf-glass-card>
|
|
|
<kf-glass-card data-role="b" dark>${this.#live(live, pool)}</kf-glass-card>
|
|
|
</div>
|
|
|
@@ -203,7 +234,7 @@ export class ViewWorkbench extends LitElement {
|
|
|
<kf-glass-card data-role="e">${this.#stat('decided', pool)}</kf-glass-card>
|
|
|
</div>
|
|
|
|
|
|
- <kf-glass-card data-role="f" class="kf-enter" style="--i:2">${this.#feed(latest)}</kf-glass-card>
|
|
|
+ <kf-glass-card data-role="f" class="kf-enter" style="--i:2">${this.#feed(latest, pool)}</kf-glass-card>
|
|
|
</div>
|
|
|
${cardFooter({ shown: latest.length, all: pool.total, filtered: false, label: '张机会卡,最近 3 张列在上方', onTop: () => window.scrollTo({ top: 0, behavior: 'smooth' }) })}
|
|
|
<!-- 只有手机显示:证据台账从底部 Tab 移到工作台页脚(真实链接,部署前缀同源) -->
|
|
|
@@ -220,11 +251,16 @@ export class ViewWorkbench extends LitElement {
|
|
|
return html`<div class="me" data-empty="no-me">
|
|
|
<div class="me-id"><div class="avatar q">?</div>
|
|
|
<div><h3>还没选「我是」</h3><span class="sub">先看全员:${live.memberCount} 人</span></div>
|
|
|
- <a class="go" href=${href('/members')} data-link>${icon('user', 14)} 选我</a></div>
|
|
|
+ <a class="go" href=${href('/members')} data-link>${icon('user', 14)} ${t('选我')}</a></div>
|
|
|
+ <div class="me-nums">
|
|
|
+ <div><b>${live.memberCount}</b><span>人</span></div>
|
|
|
+ <div><b>${live.submittedPeople}</b><span>已交</span></div>
|
|
|
+ </div>
|
|
|
<div class="stagebar" data-allstages>
|
|
|
${live.stages.map((r, i) => html`<div class="stg ${TONE[r.stage.code]}" style=${`--i:${i + 1}`} data-stage=${r.stage.code}>
|
|
|
<span class="b ${TONE[r.stage.code]}">${r.stage.code}</span>
|
|
|
<span class="n">${r.submitted || '—'}</span>
|
|
|
+ <span class="sf">${r.stage.name}</span>
|
|
|
<span class="f" aria-hidden="true"><i style=${`--v:${(r.submitted / maxSub).toFixed(2)}`}></i></span></div>`)}
|
|
|
</div>
|
|
|
<div class="empty"><span>已交 ${live.submittedPeople} 人 · 选了自己,这里换成你的阶段进度与待办</span></div>
|
|
|
@@ -250,19 +286,37 @@ export class ViewWorkbench extends LitElement {
|
|
|
return html`<div class="stg kf-enter ${TONE[s.code]} ${ok ? '' : 'todo'}" style=${`--i:${i + 1}`} data-stage=${s.code}>
|
|
|
<span class="b ${TONE[s.code]}">${s.code}</span>
|
|
|
<span class="n">${n || '—'}</span>
|
|
|
+ <span class="sf">${s.name}</span>
|
|
|
<span class="f" aria-hidden="true"><i style=${`--v:${(n / max).toFixed(2)}`}></i></span></div>`;
|
|
|
})}
|
|
|
</div>
|
|
|
${me.count === 0 ? html`<div class="empty"><span>本月还没有你的成果</span>
|
|
|
<a class="go" href=${href('/stages')} data-link>看五阶段产出 →</a></div>` : ''}
|
|
|
- ${pending.length ? html`<div>
|
|
|
- <div class="lbl">待评分 ${pending.length}</div>
|
|
|
- <ul class="pending">${pending.slice(0, 3).map((o) => html`<li>
|
|
|
- <a href=${href(`/pool/${o.id}`)} data-link>${o.title}</a><span class="s">${o.stats.avg != null ? Math.round(o.stats.avg) : '—'}</span></li>`)}</ul>
|
|
|
- </div>` : ''}
|
|
|
+ <div class="me-split">
|
|
|
+ <div>
|
|
|
+ <div class="lbl">卡池进度</div>
|
|
|
+ ${this.#progress(pool)}
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <div class="lbl">待评分${pending.length ? ` ${pending.length}` : ''}</div>
|
|
|
+ ${pending.length ? html`<ul class="pending">${pending.slice(0, 3).map((o) => html`<li>
|
|
|
+ <a href=${href(`/pool/${o.id}`)} data-link>${o.title}</a><span class="s">${o.stats.avg != null ? Math.round(o.stats.avg) : '—'}</span></li>`)}</ul>`
|
|
|
+ : html`<p class="me-none">没有等你评分的卡</p>`}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>`;
|
|
|
}
|
|
|
|
|
|
+ /** 卡池进度:候选 / 立项 / 否决 三段 + 百分比(状态用色点,不用整块色底) */
|
|
|
+ #progress(pool) {
|
|
|
+ const s = pool.byStatus;
|
|
|
+ const total = Math.max(1, pool.total);
|
|
|
+ const rows = [['候选', s.candidate, '#8A8A93'], ['已立项', s.approved, '#2F7D5B'], ['已否决', s.rejected, '#A1A1AA']];
|
|
|
+ return html`<ul class="prog">${rows.map(([k, v, c]) => html`<li>
|
|
|
+ <i class="dot" style=${`background:${c}`}></i><span class="pk">${k}</span>
|
|
|
+ <span class="pv"><b>${v}</b> 张</span><span class="pp">${Math.round((v / total) * 100)}%</span></li>`)}</ul>`;
|
|
|
+ }
|
|
|
+
|
|
|
/** B 卡:现场。深色底 + 反白字;有卡住的人 → 橙点 + 人数 + 名单;没人卡住 → 绿点 + 正向文案。 */
|
|
|
#live(live, pool) {
|
|
|
const stalled = live.stalled;
|
|
|
@@ -307,14 +361,15 @@ export class ViewWorkbench extends LitElement {
|
|
|
}
|
|
|
|
|
|
/** F:最近动过(最多 3 条,右对齐时间) */
|
|
|
- #feed(latest) {
|
|
|
+ #feed(latest, pool) {
|
|
|
return html`<div data-feed>
|
|
|
<div class="feed-head"><span class="lbl">最近动过</span>
|
|
|
<a class="go" href=${href('/pool')} data-link aria-label="全部机会卡">全部 →</a></div>
|
|
|
- ${latest.length ? html`<ul class="feed" style="margin-top:8px">${latest.map((o) => html`<li>
|
|
|
+ ${latest.length ? html`<ul class="feed">${latest.map((o) => html`<li>
|
|
|
<a href=${href(`/pool/${o.id}`)} data-link>${o.title}</a>
|
|
|
- <span class="st">${o.stageObj ? o.stageObj.code + ' ' + o.stageObj.name : ''}</span>
|
|
|
- <span class="tm">${ago(o.upload?.uploadedAt || o.createdAt)}</span></li>`)}</ul>`
|
|
|
+ <span class="st">${(o.upload?.uploadedBy || '') + (o.stageObj ? ' · ' + o.stageObj.name : '')}</span>
|
|
|
+ <span class="tm">${ago(o.upload?.uploadedAt || o.createdAt)}</span></li>`)}</ul>
|
|
|
+ <a class="feed-more go" href=${href('/pool')} data-link>看全部 ${pool.total} 张机会卡 →</a>`
|
|
|
: html`<p class="feed-empty">还没有机会卡</p>`}
|
|
|
</div>`;
|
|
|
}
|