mirror of
https://github.com/hansjone/netx.git
synced 2026-10-09 00:50:46 +08:00
Make compare sheet nav collapsible and hide run picker in fullscreen.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
4c3c034c4c
commit
28aa7cd87e
4 changed files with 267 additions and 140 deletions
|
|
@ -491,6 +491,8 @@ const en = {
|
|||
sheetCount: "Sheets",
|
||||
sheetNavTitle: "Checks",
|
||||
sheetNavHint: "{{n}} checks · click to switch",
|
||||
collapseSheetNav: "Collapse checks",
|
||||
expandSheetNav: "Expand checks",
|
||||
sheetFailCount: "{{n}} failed",
|
||||
topChangedFields: "Top failed fields",
|
||||
exportTables: "Download tables",
|
||||
|
|
|
|||
|
|
@ -488,6 +488,8 @@ const zh = {
|
|||
sheetCount: "监控表",
|
||||
sheetNavTitle: "检查项",
|
||||
sheetNavHint: "共 {{n}} 项 · 点击切换",
|
||||
collapseSheetNav: "折叠检查项",
|
||||
expandSheetNav: "展开检查项",
|
||||
sheetFailCount: "{{n}} 项失败",
|
||||
topChangedFields: "失败字段 Top",
|
||||
exportTables: "下载表格",
|
||||
|
|
|
|||
|
|
@ -11327,6 +11327,15 @@ html.login-page--paused .login-page__flare {
|
|||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.bs-cmp-board__meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.bs-cmp-board__run-select {
|
||||
flex: 0 1 280px;
|
||||
min-width: 140px;
|
||||
|
|
@ -11355,7 +11364,7 @@ html.login-page--paused .login-page__flare {
|
|||
align-items: center;
|
||||
gap: 6px 8px;
|
||||
min-width: 0;
|
||||
flex: 1 1 320px;
|
||||
flex: 1 1 280px;
|
||||
height: 32px;
|
||||
padding: 0 8px;
|
||||
border-radius: 8px;
|
||||
|
|
@ -11440,9 +11449,12 @@ html.login-page--paused .login-page__flare {
|
|||
margin-left: 0;
|
||||
}
|
||||
|
||||
.bs-cmp-board__meta {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
|
||||
.bs-cmp-sides {
|
||||
flex: 1 1 100%;
|
||||
order: 3;
|
||||
height: auto;
|
||||
min-height: 32px;
|
||||
padding: 4px 8px;
|
||||
|
|
@ -11452,6 +11464,11 @@ html.login-page--paused .login-page__flare {
|
|||
.bs-cmp-sides__name {
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.bs-cmp-board__run-select {
|
||||
flex: 1 1 200px;
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
|
||||
.bs-cmp-board__body {
|
||||
|
|
@ -11461,6 +11478,11 @@ html.login-page--paused .login-page__flare {
|
|||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
align-items: stretch;
|
||||
transition: grid-template-columns 0.18s ease;
|
||||
}
|
||||
|
||||
.bs-cmp-board__body.is-nav-collapsed {
|
||||
grid-template-columns: 44px minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.bs-cmp-nav {
|
||||
|
|
@ -11475,15 +11497,44 @@ html.login-page--paused .login-page__flare {
|
|||
|
||||
.bs-cmp-nav__head {
|
||||
display: grid;
|
||||
grid-template-columns: 8px minmax(0, 1fr) auto;
|
||||
grid-template-columns: 28px minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 0 8px;
|
||||
padding: 8px 10px;
|
||||
gap: 0 6px;
|
||||
padding: 6px 8px;
|
||||
border-bottom: 1px solid var(--bs-cmp-border);
|
||||
flex: 0 0 auto;
|
||||
min-height: 36px;
|
||||
}
|
||||
|
||||
.bs-cmp-nav.is-collapsed .bs-cmp-nav__head {
|
||||
grid-template-columns: 1fr;
|
||||
justify-items: center;
|
||||
padding: 6px 4px;
|
||||
}
|
||||
|
||||
.bs-cmp-nav__toggle {
|
||||
appearance: none;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid rgba(148, 163, 184, 0.28);
|
||||
border-radius: 6px;
|
||||
background: rgba(15, 23, 42, 0.55);
|
||||
color: #93c5fd;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.bs-cmp-nav__toggle:hover {
|
||||
background: rgba(37, 99, 235, 0.22);
|
||||
border-color: rgba(59, 130, 246, 0.45);
|
||||
}
|
||||
|
||||
.bs-cmp-nav__dot--spacer {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
|
@ -11597,6 +11648,34 @@ html.login-page--paused .login-page__flare {
|
|||
transition: background 0.12s ease, border-color 0.12s ease;
|
||||
}
|
||||
|
||||
.bs-cmp-nav.is-collapsed .bs-cmp-nav__item {
|
||||
grid-template-columns: 1fr;
|
||||
justify-items: center;
|
||||
padding: 8px 4px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.bs-cmp-nav.is-collapsed .bs-cmp-nav__name,
|
||||
.bs-cmp-nav.is-collapsed .bs-cmp-nav__stats {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.bs-cmp-nav.is-collapsed .bs-cmp-nav__list {
|
||||
padding: 4px;
|
||||
gap: 2px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.bs-cmp-nav.is-collapsed .bs-cmp-nav__dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.bs-cmp-nav.is-collapsed .bs-cmp-nav__item.is-active {
|
||||
box-shadow: none;
|
||||
background: rgba(37, 99, 235, 0.28);
|
||||
}
|
||||
|
||||
.bs-cmp-nav__item:hover {
|
||||
background: rgba(30, 41, 59, 0.75);
|
||||
}
|
||||
|
|
@ -12042,10 +12121,18 @@ html.login-page--paused .login-page__flare {
|
|||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.bs-cmp-board__body.is-nav-collapsed {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.bs-cmp-nav {
|
||||
max-height: 180px;
|
||||
}
|
||||
|
||||
.bs-cmp-nav.is-collapsed {
|
||||
max-height: 52px;
|
||||
}
|
||||
|
||||
.bs-cmp-nav__list {
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
|
|
@ -12053,6 +12140,10 @@ html.login-page--paused .login-page__flare {
|
|||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.bs-cmp-nav.is-collapsed .bs-cmp-nav__list {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.bs-cmp-nav__item {
|
||||
flex: 0 0 auto;
|
||||
min-width: 180px;
|
||||
|
|
|
|||
|
|
@ -830,6 +830,7 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
|||
const [diffsLoading, setDiffsLoading] = useState(false);
|
||||
const boardRef = useRef<HTMLDivElement | null>(null);
|
||||
const [boardFs, setBoardFs] = useState(false);
|
||||
const [navCollapsed, setNavCollapsed] = useState(false);
|
||||
const tplImportRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
|
|
@ -2693,109 +2694,119 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
|||
className={`bs-cmp-board${boardFs ? " is-fullscreen" : ""}`}
|
||||
>
|
||||
<div className="bs-cmp-board__toolbar">
|
||||
<select
|
||||
className="ui-field__select bs-cmp-board__run-select"
|
||||
aria-label={t("bizCompare.pickBatchRun")}
|
||||
value={runDetail?.id || ""}
|
||||
onChange={(e) => {
|
||||
const id = e.target.value;
|
||||
if (id) void loadRun(id);
|
||||
}}
|
||||
>
|
||||
<option value="">{t("bizCompare.pickRun")}</option>
|
||||
{runs.map((r) => {
|
||||
const before = enrichSide(
|
||||
(r as any).before,
|
||||
beforeTaskId,
|
||||
tasks,
|
||||
beforeBatches,
|
||||
);
|
||||
const after = enrichSide(
|
||||
(r as any).after,
|
||||
afterTaskId || beforeTaskId,
|
||||
tasks,
|
||||
afterBatches.length ? afterBatches : beforeBatches,
|
||||
);
|
||||
const bl = sideDeviceName(before);
|
||||
const al = sideDeviceName(after);
|
||||
const when = formatSystemTime((r as any).created_at) || "";
|
||||
return (
|
||||
<option key={r.id} value={r.id}>
|
||||
{when ? `${when} · ` : ""}
|
||||
{bl} {sideCollectTime(before)} → {al} {sideCollectTime(after)}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
{runs.length ? (
|
||||
<span className="muted bs-cmp-board__run-count">
|
||||
{t("bizCompare.runCount", { n: String(runs.length) })}
|
||||
</span>
|
||||
) : null}
|
||||
{runDetail ? (
|
||||
<div className="bs-cmp-sides" aria-label={t("bizCompare.sidesTitle")}>
|
||||
{(() => {
|
||||
const before = enrichSide(
|
||||
runDetail.before,
|
||||
beforeTaskId,
|
||||
tasks,
|
||||
beforeBatches,
|
||||
);
|
||||
const after = enrichSide(
|
||||
runDetail.after,
|
||||
afterTaskId || beforeTaskId,
|
||||
tasks,
|
||||
afterBatches.length ? afterBatches : beforeBatches,
|
||||
);
|
||||
const beforeName = sideDeviceName(before);
|
||||
const afterName = sideDeviceName(after);
|
||||
const beforeTime = sideCollectTime(before);
|
||||
const afterTime = sideCollectTime(after);
|
||||
return (
|
||||
<>
|
||||
<div className="bs-cmp-sides__side is-before">
|
||||
<span className="bs-cmp-sides__tag">{t("bizCompare.sideBefore")}</span>
|
||||
<strong className="bs-cmp-sides__name" title={`${beforeName} ${beforeTime}`}>
|
||||
{beforeName}
|
||||
</strong>
|
||||
<span className="bs-cmp-sides__meta" title={beforeTime}>
|
||||
{beforeTime}
|
||||
<div className="bs-cmp-board__meta">
|
||||
{runDetail ? (
|
||||
<div className="bs-cmp-sides" aria-label={t("bizCompare.sidesTitle")}>
|
||||
{(() => {
|
||||
const before = enrichSide(
|
||||
runDetail.before,
|
||||
beforeTaskId,
|
||||
tasks,
|
||||
beforeBatches,
|
||||
);
|
||||
const after = enrichSide(
|
||||
runDetail.after,
|
||||
afterTaskId || beforeTaskId,
|
||||
tasks,
|
||||
afterBatches.length ? afterBatches : beforeBatches,
|
||||
);
|
||||
const beforeName = sideDeviceName(before);
|
||||
const afterName = sideDeviceName(after);
|
||||
const beforeTime = sideCollectTime(before);
|
||||
const afterTime = sideCollectTime(after);
|
||||
return (
|
||||
<>
|
||||
<div className="bs-cmp-sides__side is-before">
|
||||
<span className="bs-cmp-sides__tag">{t("bizCompare.sideBefore")}</span>
|
||||
<strong className="bs-cmp-sides__name" title={`${beforeName} ${beforeTime}`}>
|
||||
{beforeName}
|
||||
</strong>
|
||||
<span className="bs-cmp-sides__meta" title={beforeTime}>
|
||||
{beforeTime}
|
||||
</span>
|
||||
</div>
|
||||
<span className="bs-cmp-sides__arrow" aria-hidden>
|
||||
→
|
||||
</span>
|
||||
</div>
|
||||
<span className="bs-cmp-sides__arrow" aria-hidden>
|
||||
→
|
||||
</span>
|
||||
<div className="bs-cmp-sides__side is-after">
|
||||
<span className="bs-cmp-sides__tag">{t("bizCompare.sideAfter")}</span>
|
||||
<strong className="bs-cmp-sides__name" title={`${afterName} ${afterTime}`}>
|
||||
{afterName}
|
||||
</strong>
|
||||
<span className="bs-cmp-sides__meta" title={afterTime}>
|
||||
{afterTime}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
) : null}
|
||||
<div className="bs-cmp-sides__side is-after">
|
||||
<span className="bs-cmp-sides__tag">{t("bizCompare.sideAfter")}</span>
|
||||
<strong className="bs-cmp-sides__name" title={`${afterName} ${afterTime}`}>
|
||||
{afterName}
|
||||
</strong>
|
||||
<span className="bs-cmp-sides__meta" title={afterTime}>
|
||||
{afterTime}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
) : null}
|
||||
{!boardFs ? (
|
||||
<>
|
||||
<select
|
||||
className="ui-field__select bs-cmp-board__run-select"
|
||||
aria-label={t("bizCompare.pickBatchRun")}
|
||||
value={runDetail?.id || ""}
|
||||
onChange={(e) => {
|
||||
const id = e.target.value;
|
||||
if (id) void loadRun(id);
|
||||
}}
|
||||
>
|
||||
<option value="">{t("bizCompare.pickRun")}</option>
|
||||
{runs.map((r) => {
|
||||
const before = enrichSide(
|
||||
(r as any).before,
|
||||
beforeTaskId,
|
||||
tasks,
|
||||
beforeBatches,
|
||||
);
|
||||
const after = enrichSide(
|
||||
(r as any).after,
|
||||
afterTaskId || beforeTaskId,
|
||||
tasks,
|
||||
afterBatches.length ? afterBatches : beforeBatches,
|
||||
);
|
||||
const bl = sideDeviceName(before);
|
||||
const al = sideDeviceName(after);
|
||||
const when = formatSystemTime((r as any).created_at) || "";
|
||||
return (
|
||||
<option key={r.id} value={r.id}>
|
||||
{when ? `${when} · ` : ""}
|
||||
{bl} {sideCollectTime(before)} → {al} {sideCollectTime(after)}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
{runs.length ? (
|
||||
<span className="muted bs-cmp-board__run-count">
|
||||
{t("bizCompare.runCount", { n: String(runs.length) })}
|
||||
</span>
|
||||
) : null}
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="btn-row bs-cmp-board__actions">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
isDisabled={busy || !runDetail?.id}
|
||||
onPress={() => void downloadRunTables()}
|
||||
>
|
||||
{t("bizCompare.exportTables")}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="danger"
|
||||
isDisabled={busy || !runDetail?.id}
|
||||
onPress={() => void removeRun(String(runDetail?.id || ""))}
|
||||
>
|
||||
{t("bizCompare.deleteRun")}
|
||||
</Button>
|
||||
{!boardFs ? (
|
||||
<>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
isDisabled={busy || !runDetail?.id}
|
||||
onPress={() => void downloadRunTables()}
|
||||
>
|
||||
{t("bizCompare.exportTables")}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="danger"
|
||||
isDisabled={busy || !runDetail?.id}
|
||||
onPress={() => void removeRun(String(runDetail?.id || ""))}
|
||||
>
|
||||
{t("bizCompare.deleteRun")}
|
||||
</Button>
|
||||
</>
|
||||
) : null}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
|
|
@ -2808,41 +2819,62 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
|
|||
</div>
|
||||
|
||||
{runDetail ? (
|
||||
<div className="bs-cmp-board__body">
|
||||
<aside className="bs-cmp-nav" aria-label={t("bizCompare.sheetNavTitle")}>
|
||||
<div
|
||||
className={`bs-cmp-board__body${navCollapsed ? " is-nav-collapsed" : ""}`}
|
||||
>
|
||||
<aside
|
||||
className={`bs-cmp-nav${navCollapsed ? " is-collapsed" : ""}`}
|
||||
aria-label={t("bizCompare.sheetNavTitle")}
|
||||
>
|
||||
<div className="bs-cmp-nav__head">
|
||||
<span className="bs-cmp-nav__dot bs-cmp-nav__dot--spacer" aria-hidden />
|
||||
<span className="bs-cmp-nav__head-main">
|
||||
<span className="bs-cmp-nav__title">{t("bizCompare.sheetNavTitle")}</span>
|
||||
<span
|
||||
className="bs-cmp-nav__count"
|
||||
title={t("bizCompare.sheetNavHint", {
|
||||
n: String(sheetCards.length || runSheets.length),
|
||||
})}
|
||||
>
|
||||
{sheetCards.length || runSheets.length}
|
||||
</span>
|
||||
</span>
|
||||
<span className="bs-cmp-nav__legend" aria-hidden>
|
||||
<span
|
||||
className="bs-cmp-nav__num bs-cmp-nav__num--fail is-hot"
|
||||
title={t("bizCompare.kindFail")}
|
||||
>
|
||||
{t("bizCompare.kindFail")}
|
||||
</span>
|
||||
<span
|
||||
className="bs-cmp-nav__num bs-cmp-nav__num--ok is-hot"
|
||||
title={t("bizCompare.kindSuccess")}
|
||||
>
|
||||
{t("bizCompare.kindSuccess")}
|
||||
</span>
|
||||
<span
|
||||
className="bs-cmp-nav__num bs-cmp-nav__num--rate"
|
||||
title={t("bizCompare.passRateShort")}
|
||||
>
|
||||
%
|
||||
</span>
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
className="bs-cmp-nav__toggle"
|
||||
aria-expanded={!navCollapsed}
|
||||
title={
|
||||
navCollapsed
|
||||
? t("bizCompare.expandSheetNav")
|
||||
: t("bizCompare.collapseSheetNav")
|
||||
}
|
||||
onClick={() => setNavCollapsed((v) => !v)}
|
||||
>
|
||||
{navCollapsed ? "»" : "«"}
|
||||
</button>
|
||||
{!navCollapsed ? (
|
||||
<>
|
||||
<span className="bs-cmp-nav__head-main">
|
||||
<span className="bs-cmp-nav__title">{t("bizCompare.sheetNavTitle")}</span>
|
||||
<span
|
||||
className="bs-cmp-nav__count"
|
||||
title={t("bizCompare.sheetNavHint", {
|
||||
n: String(sheetCards.length || runSheets.length),
|
||||
})}
|
||||
>
|
||||
{sheetCards.length || runSheets.length}
|
||||
</span>
|
||||
</span>
|
||||
<span className="bs-cmp-nav__legend" aria-hidden>
|
||||
<span
|
||||
className="bs-cmp-nav__num bs-cmp-nav__num--fail is-hot"
|
||||
title={t("bizCompare.kindFail")}
|
||||
>
|
||||
{t("bizCompare.kindFail")}
|
||||
</span>
|
||||
<span
|
||||
className="bs-cmp-nav__num bs-cmp-nav__num--ok is-hot"
|
||||
title={t("bizCompare.kindSuccess")}
|
||||
>
|
||||
{t("bizCompare.kindSuccess")}
|
||||
</span>
|
||||
<span
|
||||
className="bs-cmp-nav__num bs-cmp-nav__num--rate"
|
||||
title={t("bizCompare.passRateShort")}
|
||||
>
|
||||
%
|
||||
</span>
|
||||
</span>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="bs-cmp-nav__list" role="tablist">
|
||||
{(sheetCards.length
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue