diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 94d4bb3..e02e6bd 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -22,6 +22,13 @@ --danger-soft: #fef2f2; --danger-soft-border: #fecaca; --warning-soft: #fffbeb; + /* 内容页布局尺度:顶栏高度、内容列左右留白、内容列最大宽度 */ + --layout-header-h: 56px; + --content-pad: 14px; + /* 底部留白单独一个变量:.app-content 的下边距比四周略大一点, + 阅读器高度等「满屏高度」计算时统一引用它,避免出现几像素的竖直滚动条。 */ + --content-pad-end: calc(var(--content-pad) + 4px); + --content-max: 1680px; } /* 深色模式:只换中性色,品牌色与语义色保持不变 */ @@ -269,6 +276,13 @@ body { position: sticky; top: 0; z-index: 20; + /* 与 .app-content-inner 同一条基线:内容列被 max-width 收窄时, + 顶栏控件同步内收(推导:内容列左偏移 = pad + (100% - 2*pad - 上限)/2 + = (100% - 上限)/2,未被夹住时就是 pad,故取两者较大值)。 */ + padding-inline: max( + var(--content-pad), + calc((100% - var(--content-max)) / 2) + ) !important; background: var(--header-bg); border-bottom: 1px solid var(--border-soft); backdrop-filter: saturate(180%) blur(8px); @@ -467,12 +481,33 @@ body { color: var(--text-strong); } +/* 内容页边距:留白随视口变宽略微加大,但宽度优先给内容, + 避免高分辨屏上「中间一条窄柱、两侧大片空白」。 */ +@media (min-width: 900px) { + :root { + --content-pad: 20px; + } +} + +@media (min-width: 1600px) { + :root { + --content-pad: 24px; + } +} + +@media (min-width: 2200px) { + :root { + --content-pad: 28px; + } +} + .app-content { - padding: 20px; + padding: var(--content-pad); + padding-block-end: var(--content-pad-end); } .app-content-inner { - max-width: 1360px; + max-width: min(100%, var(--content-max)); margin: 0 auto; } @@ -986,7 +1021,10 @@ body { .ebook-reader { display: flex; flex-direction: column; - height: calc(100vh - 96px); + /* 顶栏 + 内容区上下留白之外的全部空间,保证页面本身不产生竖直滚动 */ + height: calc( + 100vh - var(--layout-header-h) - var(--content-pad) - var(--content-pad-end) + ); min-height: 480px; overflow: hidden; background: var(--surface); @@ -1138,12 +1176,16 @@ body { } @media (max-width: 768px) { + :root { + --content-pad-end: calc(var(--content-pad) + 2px); + } + .app-content { - padding: 14px; + padding: var(--content-pad); + padding-block-end: var(--content-pad-end); } .ebook-reader { - height: calc(100vh - 92px); min-height: 420px; } @@ -1288,3 +1330,741 @@ body { .daily-submit.ant-btn:disabled { box-shadow: none; } + +/* ---------------- 分屏工作台(题库 / 记录 / 错题 / 答题) ---------------- */ + +.split-view { + display: flex; + align-items: stretch; + overflow: hidden; + background: var(--surface); + border: 1px solid var(--border-soft); + border-radius: 14px; + box-shadow: 0 1px 2px rgba(31, 32, 51, 0.04); +} + +.split-aside { + flex: 0 0 auto; + min-width: 0; + display: flex; + flex-direction: column; + overflow: hidden; + background: var(--surface-2); +} + +.split-view--left .split-aside { + border-inline-end: 1px solid var(--border-soft); +} + +.split-view--right .split-aside { + border-inline-start: 1px solid var(--border-soft); +} + +.split-main { + flex: 1 1 auto; + min-width: 0; + display: flex; + flex-direction: column; + overflow: hidden; +} + +/* 窄屏:左右分屏退回上下堆叠,清单只占一小段高度 */ +.split-view--stack { + flex-direction: column; + height: auto !important; +} + +.split-view--stack .split-aside { + border-inline-end: 0; + border-inline-start: 0; + border-bottom: 1px solid var(--border-soft); + width: 100% !important; + max-height: 40vh; +} + +.split-view--stack .split-main { + min-height: 60vh; +} + +.split-resizer { + flex: 0 0 7px; + cursor: col-resize; + position: relative; + background: transparent; + touch-action: none; +} + +.split-resizer::after { + content: ""; + position: absolute; + inset: 0 3px; + border-radius: 3px; + background: var(--border-soft); + transition: background 0.16s ease; +} + +.split-resizer:hover::after, +.split-resizer--active::after { + background: var(--brand); +} + +body.split-dragging { + cursor: col-resize; + user-select: none; +} + +/* 分屏里的一栏 */ +.pane { + display: flex; + flex-direction: column; + height: 100%; + min-height: 0; +} + +.pane-head { + flex: 0 0 auto; + padding: 12px 14px; + border-bottom: 1px solid var(--border-soft); + background: var(--surface); +} + +.split-aside > .pane > .pane-head { + background: var(--surface-2); +} + +.pane-head-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; +} + +.pane-title { + font-size: 14px; + font-weight: 600; + color: var(--text-strong); + min-width: 0; +} + +.pane-extra { + flex-shrink: 0; +} + +.pane-toolbar { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px; + margin-top: 10px; +} + +.pane-toolbar:empty { + display: none; +} + +/* 题库侧栏的三个筛选下拉尽量等分一行;栏太窄时整体换行, + 而不是把「难度不限」压成「难度…」; + 菜单宽度靠 popupMatchSelectWidth={false} 单独放开,长选项不会被裁。 */ +.bank-filter-select { + flex: 1 1 92px; + min-width: 92px; +} + +.pane-body { + flex: 1 1 auto; + min-height: 0; + overflow: auto; + padding: 10px 12px 18px; +} + +.pane--flat .pane-body { + padding: 0; + overflow: hidden; + display: flex; +} + +.pane-foot { + flex: 0 0 auto; + padding: 10px 14px; + border-top: 1px solid var(--border-soft); + background: var(--surface); +} + +/* 清单条目 */ +.master-item { + display: block; + width: 100%; + text-align: start; + padding: 10px 12px; + margin-bottom: 6px; + border: 1px solid transparent; + border-radius: 10px; + background: transparent; + color: inherit; + cursor: pointer; + transition: background 0.16s ease, border-color 0.16s ease; +} + +.master-item:hover { + background: var(--surface-hover); +} + +.master-item--active { + background: var(--brand-soft); + border-color: rgba(91, 92, 226, 0.35); +} + +.master-item-title { + display: flex; + align-items: center; + gap: 6px; + font-weight: 600; + color: var(--text-strong); + line-height: 1.5; +} + +.master-item-sub { + margin-top: 3px; + font-size: 12px; + color: var(--text-secondary); + line-height: 1.6; +} + +.master-group { + margin: 6px 0 4px; + padding: 0 4px; + font-size: 12px; + font-weight: 600; + color: var(--text-muted); +} + +/* 得分环 / 计数的数字都用等宽数字,纵向对比才整齐 */ +.tabular { + font-variant-numeric: tabular-nums; +} + +/* ---------------- 试卷排版 ---------------- */ + +.paper { + display: flex; + flex-direction: column; + flex: 1 1 auto; + min-height: 0; + min-width: 0; +} + +.paper-head { + flex: 0 0 auto; + padding: 14px 20px 10px; + border-bottom: 1px solid var(--border-soft); + background: var(--surface); +} + +.paper-head-main { + min-width: 0; +} + +.paper-title { + font-size: 16px; + font-weight: 650; + color: var(--text-strong); + line-height: 1.5; +} + +.paper-meta { + margin-top: 2px; + font-size: 12.5px; + color: var(--text-secondary); + line-height: 1.7; +} + +.paper-head-side { + flex-shrink: 0; +} + +.paper-score { + display: flex; + flex-direction: column; + align-items: flex-end; + padding: 0 12px; + border-inline-start: 1px solid var(--border-soft); +} + +.paper-score-value { + font-size: 22px; + font-weight: 700; + line-height: 1.1; + color: var(--brand); + font-variant-numeric: tabular-nums; +} + +.paper-score-total { + font-size: 13px; + font-weight: 500; + color: var(--text-secondary); +} + +.paper-score-label { + font-size: 11.5px; + color: var(--text-secondary); +} + +.paper-head-tools { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + flex-wrap: wrap; + margin-top: 12px; +} + +.paper-body { + flex: 1 1 auto; + min-height: 0; + overflow: auto; + padding: 14px 18px 24px; +} + +/* 一道题 */ +.paper-question { + display: flex; + gap: 12px; + padding: 14px 10px 16px; + border-radius: 12px; + transition: background 0.16s ease, box-shadow 0.16s ease; +} + +.paper-question + .paper-question { + border-top: 1px dashed var(--border-soft); + border-radius: 12px 12px 0 0; +} + +.paper-question--active { + background: var(--surface-2); + box-shadow: inset 3px 0 0 var(--brand); +} + +.paper-question-rail { + flex: 0 0 auto; + padding-top: 1px; +} + +.paper-question-no { + display: inline-grid; + place-items: center; + min-width: 28px; + height: 28px; + padding: 0 6px; + border-radius: 50%; + background: var(--track); + color: var(--text-strong); + font-size: 13px; + font-weight: 700; + font-variant-numeric: tabular-nums; +} + +.paper-question--correct .paper-question-no { + background: var(--success-soft); + color: var(--success-soft-border); + color: #16a34a; +} + +.paper-question--wrong .paper-question-no { + background: var(--danger-soft); + color: #dc2626; +} + +.paper-question-main { + flex: 1 1 auto; + min-width: 0; +} + +.paper-question-head { + min-height: 24px; + margin-bottom: 2px; +} + +.paper-question-actions { + margin-inline-start: auto; +} + +.paper-stem { + font-size: 15.5px; + font-weight: 600; + line-height: 1.85; + color: var(--text-strong); + margin: 6px 0 12px; +} + +.paper-options { + max-width: 760px; +} + +.paper-answer { + margin-top: 12px; + padding: 10px 12px; + border-radius: 10px; + background: var(--surface-2); + border: 1px solid var(--border-soft); + max-width: 760px; +} + +.paper-explain-body { + margin-top: 10px; + padding-top: 10px; + border-top: 1px dashed var(--border-soft); + line-height: 1.85; + color: var(--text-strong); +} + +.paper-explain-toggle { + border: 0; + background: transparent; + padding: 0; + cursor: pointer; + color: var(--brand); + font-size: 13px; +} + +/* 答题卡 */ +.answer-sheet { + display: flex; + align-items: center; + gap: 14px; + flex-wrap: wrap; +} + +.answer-sheet-chips { + display: flex; + align-items: center; + gap: 6px; + flex-wrap: wrap; +} + +.answer-chip { + position: relative; + display: inline-flex; + align-items: center; + gap: 3px; + min-width: 32px; + height: 30px; + padding: 0 8px; + border: 1px solid var(--border-soft); + border-radius: 9px; + background: var(--surface); + color: var(--text-strong); + font-size: 13px; + font-variant-numeric: tabular-nums; + cursor: pointer; + transition: border-color 0.16s ease, background 0.16s ease, transform 0.16s ease; +} + +.answer-chip:hover { + transform: translateY(-1px); + border-color: var(--brand); +} + +.answer-chip--active { + border-color: var(--brand); + box-shadow: 0 0 0 2px var(--brand-soft); +} + +.answer-chip-icon { + font-size: 11px; +} + +.answer-chip--correct { + background: var(--success-soft); + border-color: var(--success-soft-border); + color: #16a34a; +} + +.answer-chip--wrong { + background: var(--danger-soft); + border-color: var(--danger-soft-border); + color: #dc2626; +} + +.answer-chip--done { + border-color: rgba(91, 92, 226, 0.45); + color: var(--brand); +} + +.answer-chip--pending { + border-style: dashed; + color: var(--text-muted); +} + +.answer-sheet-legend { + display: flex; + align-items: center; + gap: 10px; + font-size: 12px; + color: var(--text-secondary); +} + +.answer-sheet-legend-toggle { + border: 0; + background: transparent; + padding: 0; + cursor: pointer; + color: var(--text-secondary); + font-size: 12px; + text-decoration: underline dotted; +} + +.answer-sheet-legend-items { + display: flex; +} + +.answer-sheet-legend-items span { + display: inline-flex; + align-items: center; + gap: 4px; +} + +.answer-dot { + width: 9px; + height: 9px; + border-radius: 3px; + display: inline-block; +} + +.answer-dot--correct { + background: var(--success-soft); + border: 1px solid #16a34a; +} + +.answer-dot--wrong { + background: var(--danger-soft); + border: 1px solid #dc2626; +} + +.answer-dot--done { + background: var(--brand-soft); + border: 1px solid var(--brand); +} + +.answer-dot--pending { + background: transparent; + border: 1px dashed var(--text-muted); +} + +.answer-sheet-summary { + margin-inline-start: auto; + display: flex; + align-items: center; + gap: 10px; + font-size: 12.5px; + color: var(--text-secondary); +} + +/* 答题中的提交条 */ +.paper-submit { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + flex-wrap: wrap; +} + +/* 题库卡片里的章节导航 */ +.chapter-nav-count { + font-variant-numeric: tabular-nums; + color: var(--text-secondary); + font-size: 12px; +} + + +/* 试卷头部:标题与视图切换并排 */ +.paper-head-row { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 12px; + flex-wrap: wrap; +} + +/* 题干下方的主体(选项 / 作答控件) */ +.paper-body-slot { + min-width: 0; +} + +/* 可折叠解析 */ +.paper-explain { + margin-top: 12px; + max-width: 760px; +} + +.paper-explain-caret { + margin-inline-start: 6px; + font-size: 10px; + color: var(--text-muted); +} + +/* 我的作答 / 正确答案 对照 */ +.answer-compare { + align-items: center; + row-gap: 4px; +} + +.answer-chip-no { + font-variant-numeric: tabular-nums; +} + +/* 清单里的可点分组(教材名)与更紧凑的子项(章节) */ +.master-group-button { + display: flex; + align-items: center; + gap: 6px; + width: 100%; + padding: 5px 8px; + border: 0; + border-radius: 8px; + background: transparent; + color: var(--text-strong); + font: inherit; + text-align: start; + cursor: pointer; + transition: background 0.16s ease, color 0.16s ease; +} + +.master-group-button:hover { + background: var(--surface-hover); +} + +.master-group-button--active { + color: var(--brand); +} + +.master-item--tight { + padding: 7px 10px; + margin-bottom: 2px; + border-radius: 8px; +} + +.master-item--tight .master-item-title { + font-weight: 500; + font-size: 13px; +} + +/* 侧栏底部的小字提示 */ +.aside-hint { + margin-top: 14px; + padding: 10px 4px 0; + border-top: 1px dashed var(--border-soft); + line-height: 1.7; +} + +/* 生成任务进度条(挂在分屏上方,高度变化由 ResizeObserver 收敛) */ +.bank-task { + margin-bottom: 12px; + padding: 12px 16px 4px; + background: var(--surface); + border: 1px solid var(--border-soft); + border-radius: 12px; +} + +/* 成绩单摘要 */ +.result-score { + display: flex; + align-items: baseline; + gap: 8px; + margin-bottom: 8px; +} + +.result-score-value { + font-size: 30px; + font-weight: 700; + line-height: 1.1; + color: var(--brand); + font-variant-numeric: tabular-nums; +} + +.result-score-total { + font-size: 15px; + font-weight: 500; + color: var(--text-secondary); +} + +.result-score-label { + font-size: 12px; + color: var(--text-secondary); +} + +.result-knowledge { + margin-top: 14px; + padding-top: 4px; + border-top: 1px dashed var(--border-soft); +} + +.result-knowledge-row { + gap: 8px; + padding: 4px 0; + font-size: 13px; +} + +.result-knowledge-name { + flex: 1 1 auto; + min-width: 0; + color: var(--text-strong); +} + + +/* 章节测试:组卷条件收成上方一条窄栏,高度留给答题工作台 */ +.test-launcher { + padding: 12px 14px; + background: var(--surface); + border: 1px solid var(--border-soft); + border-radius: 14px; + box-shadow: 0 1px 2px rgba(31, 32, 51, 0.04); +} + +.test-launcher-row { + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; +} + +.test-launcher-select { + width: 260px; + max-width: 100%; +} + +.test-launcher-meta { + display: flex; + align-items: center; + gap: 12px; + flex-wrap: wrap; + margin-top: 10px; + padding-top: 10px; + border-top: 1px dashed var(--border-soft); +} + +.test-launcher-tags { + display: inline-flex; + align-items: center; + flex-wrap: wrap; + gap: 4px; +} + +.test-launcher-note { + flex: 1 1 240px; + min-width: 0; + font-size: 12.5px; +} + +@media (max-width: 1200px) { + .paper-head { + padding: 12px 14px 8px; + } + + .paper-body { + padding: 14px 12px 26px; + } + + .paper-question { + padding: 16px 6px 18px; + } +}