fix(题目页): 例子输出长一点就在半格里横着滚,机房屏幕上一成的题看不全
机房两种屏上左栏只有 550px 左右,「例子 N | 输入 | 输出」每格只放得下二十来个字符, 而例子输出最长那行的 p90 有 28 个字符宽(中文按两个算),62 道题超过 32。 按这道题最长那行用实际字体量宽度:半格放得下照旧摆表;放不下就整道题的例子改成 上下排,输入、输出各占满宽;整宽也放不下的二十来道才横着滚,滚动条改成 6px 细条。 拖动分栏时实时重算。不自动折行:折出来的行学生分不清是不是真换了行。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cd75d42a64
commit
c497ccadc8
1 file changed
+127
-16
@@ -111,6 +111,64 @@ watch(
|
|||||||
const samples = computed(() => problem.value?.samples ?? [])
|
const samples = computed(() => problem.value?.samples ?? [])
|
||||||
const submissionStore = useSubmissionStore()
|
const submissionStore = useSubmissionStore()
|
||||||
|
|
||||||
|
const SAMPLE_SIDES = ["input", "output"] as const
|
||||||
|
const SAMPLE_SIDE_LABEL = { input: "输入", output: "输出" } as const
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 例子默认摆成「例子 N | 输入 | 输出」的表,可机房那两种屏上左栏只有 550px 左右,每格只放得下
|
||||||
|
* 二十来个字符,而例子输出最长那行的 p90 就有 28 个字符宽(中文按两个算)—— 一成的题会在
|
||||||
|
* 半格里横着滚。所以按这道题最长那行量一下:半格放得下就摆表,放不下这道题的例子就整体改成
|
||||||
|
* 上下排(输入一行、输出一行,各占满宽)。整宽还放不下的二十来道才横着滚。不自动折行:折出来
|
||||||
|
* 的行学生分不清是不是真换了行,照着它输出就错了。
|
||||||
|
* 下面几个尺寸 CSS 里也用(v-bind),量宽度和排版是同一套数。
|
||||||
|
*/
|
||||||
|
const SAMPLE_FONT = "14px Consolas, Monaco, monospace"
|
||||||
|
const SAMPLE_NO_W = 56
|
||||||
|
const SAMPLE_GAP = 8
|
||||||
|
const SAMPLE_PAD_L = 10
|
||||||
|
const SAMPLE_PAD_R = 4
|
||||||
|
const SAMPLE_COPY_W = 44
|
||||||
|
const sampleCss = {
|
||||||
|
font: SAMPLE_FONT,
|
||||||
|
noW: `${SAMPLE_NO_W}px`,
|
||||||
|
gap: `${SAMPLE_GAP}px`,
|
||||||
|
pad: `2px ${SAMPLE_PAD_R}px 2px ${SAMPLE_PAD_L}px`,
|
||||||
|
copyW: `${SAMPLE_COPY_W}px`,
|
||||||
|
}
|
||||||
|
|
||||||
|
let measureCtx: CanvasRenderingContext2D | null = null
|
||||||
|
/** 例子里最长那一行排出来有多宽(px)。等宽字体里中文、表情照样比英文宽,所以按字体真量 */
|
||||||
|
const sampleTextWidth = computed(() => {
|
||||||
|
measureCtx ??= document.createElement("canvas").getContext("2d")
|
||||||
|
if (!measureCtx) return 0
|
||||||
|
measureCtx.font = SAMPLE_FONT
|
||||||
|
let widest = 0
|
||||||
|
for (const sample of samples.value) {
|
||||||
|
for (const side of SAMPLE_SIDES) {
|
||||||
|
for (const line of sample[side].split("\n")) {
|
||||||
|
// <pre> 里 tab 按 8 列排
|
||||||
|
const width = measureCtx.measureText(line.replace(/\t/g, " ")).width
|
||||||
|
if (width > widest) widest = width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return widest
|
||||||
|
})
|
||||||
|
|
||||||
|
const sampleTable = useTemplateRef<HTMLElement>("sampleTable")
|
||||||
|
const { width: sampleTableWidth } = useElementSize(sampleTable)
|
||||||
|
/** 摆成表时,半格里留给文字的宽度 */
|
||||||
|
const sampleHalfWidth = computed(
|
||||||
|
() =>
|
||||||
|
(sampleTableWidth.value - SAMPLE_NO_W - SAMPLE_GAP * 2) / 2 -
|
||||||
|
SAMPLE_PAD_L -
|
||||||
|
SAMPLE_PAD_R -
|
||||||
|
SAMPLE_COPY_W,
|
||||||
|
)
|
||||||
|
const samplesStacked = computed(
|
||||||
|
() => sampleTableWidth.value > 0 && sampleTextWidth.value > sampleHalfWidth.value,
|
||||||
|
)
|
||||||
|
|
||||||
// 文案和配色分类都由后端生成 —— 原来这里有一份 NODE_TARGET_LABELS +
|
// 文案和配色分类都由后端生成 —— 原来这里有一份 NODE_TARGET_LABELS +
|
||||||
// ruleDescription + ruleTagType,和判题机那份几乎一模一样,见契约
|
// ruleDescription + ruleTagType,和判题机那份几乎一模一样,见契约
|
||||||
// astRequirementSchema。规则原文(engine / target)不下发给学生。
|
// astRequirementSchema。规则原文(engine / target)不下发给学生。
|
||||||
@@ -282,23 +340,31 @@ const astRequirements = computed(() => Object.entries(problem.value?.astRequirem
|
|||||||
自己输入数据试试 ›
|
自己输入数据试试 ›
|
||||||
</n-button>
|
</n-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="sample-table" role="table" aria-label="例子">
|
<!-- 半格放不下最长那行就整体改成上下排,见 samplesStacked -->
|
||||||
|
<div
|
||||||
|
ref="sampleTable"
|
||||||
|
class="sample-table"
|
||||||
|
:class="{ stacked: samplesStacked }"
|
||||||
|
role="table"
|
||||||
|
aria-label="例子"
|
||||||
|
>
|
||||||
|
<div v-if="!samplesStacked" class="sample-row" role="row">
|
||||||
<span role="columnheader"></span>
|
<span role="columnheader"></span>
|
||||||
<span role="columnheader" class="sample-label">输入</span>
|
<span role="columnheader" class="sample-label">输入</span>
|
||||||
<span role="columnheader" class="sample-label">输出</span>
|
<span role="columnheader" class="sample-label">输出</span>
|
||||||
<template v-for="(sample, index) of samples" :key="index">
|
</div>
|
||||||
|
<div v-for="(sample, index) of samples" :key="index" class="sample-row" role="row">
|
||||||
<span class="sample-no" role="rowheader">例子 {{ index + 1 }}</span>
|
<span class="sample-no" role="rowheader">例子 {{ index + 1 }}</span>
|
||||||
<div
|
<template v-for="side in SAMPLE_SIDES" :key="side">
|
||||||
v-for="side in ['input', 'output'] as const"
|
<span v-if="samplesStacked" class="sample-label sample-side">
|
||||||
:key="side"
|
{{ SAMPLE_SIDE_LABEL[side] }}
|
||||||
class="sample-cell"
|
</span>
|
||||||
role="cell"
|
<div class="sample-cell" role="cell">
|
||||||
>
|
|
||||||
<pre class="testcase">{{ sample[side] }}</pre>
|
<pre class="testcase">{{ sample[side] }}</pre>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="sample-copy"
|
class="sample-copy"
|
||||||
:aria-label="`复制例子 ${index + 1} 的${side === 'input' ? '输入' : '输出'}`"
|
:aria-label="`复制例子 ${index + 1} 的${SAMPLE_SIDE_LABEL[side]}`"
|
||||||
@click="copySample(`${index}-${side}`, sample[side])"
|
@click="copySample(`${index}-${side}`, sample[side])"
|
||||||
>
|
>
|
||||||
{{ copiedKey === `${index}-${side}` ? "已复制" : "复制" }}
|
{{ copiedKey === `${index}-${side}` ? "已复制" : "复制" }}
|
||||||
@@ -306,6 +372,7 @@ const astRequirements = computed(() => Object.entries(problem.value?.astRequirem
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -509,14 +576,31 @@ const astRequirements = computed(() => Object.entries(problem.value?.astRequirem
|
|||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 例子:例子 N | 输入 | 输出,每格右边一个「复制」 */
|
/*
|
||||||
|
* 例子:例子 N | 输入 | 输出,每格右边一个「复制」。
|
||||||
|
* 放不下时(.stacked)每个例子两行:例子 N | 输入 | 那格,下一行 输出 | 那格
|
||||||
|
*/
|
||||||
.sample-table {
|
.sample-table {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sample-table.stacked {
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sample-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 56px minmax(0, 1fr) minmax(0, 1fr);
|
grid-template-columns: v-bind("sampleCss.noW") minmax(0, 1fr) minmax(0, 1fr);
|
||||||
gap: 6px 8px;
|
gap: 6px v-bind("sampleCss.gap");
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stacked .sample-row {
|
||||||
|
grid-template-columns: v-bind("sampleCss.noW") 32px minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
.sample-label,
|
.sample-label,
|
||||||
.sample-no {
|
.sample-no {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
@@ -528,11 +612,23 @@ const astRequirements = computed(() => Object.entries(problem.value?.astRequirem
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 上下排时「例子 N」竖跨输入、输出两行,和「输入」对齐在顶上 */
|
||||||
|
.stacked .sample-no {
|
||||||
|
grid-row: span 2;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stacked .sample-no,
|
||||||
|
.sample-side {
|
||||||
|
padding-top: 8px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
.sample-cell {
|
.sample-cell {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
padding: 2px 4px 2px 10px;
|
padding: v-bind("sampleCss.pad");
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
background-color: rgba(128, 128, 128, 0.08);
|
background-color: rgba(128, 128, 128, 0.08);
|
||||||
}
|
}
|
||||||
@@ -542,17 +638,32 @@ const astRequirements = computed(() => Object.entries(problem.value?.astRequirem
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 4px 0;
|
padding: 4px 0;
|
||||||
font-family: Consolas, Monaco, monospace;
|
font: v-bind("sampleCss.font");
|
||||||
font-size: 14px;
|
line-height: 1.5;
|
||||||
white-space: pre;
|
white-space: pre;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 整宽也放不下、只好横着滚的那二十来道:Windows 上默认那根带箭头的粗滚动条比例子本身还显眼 */
|
||||||
|
.testcase::-webkit-scrollbar {
|
||||||
|
height: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.testcase::-webkit-scrollbar-thumb {
|
||||||
|
border-radius: 3px;
|
||||||
|
background-color: rgba(128, 128, 128, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.testcase::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
.sample-copy {
|
.sample-copy {
|
||||||
flex: none;
|
flex: none;
|
||||||
|
width: v-bind("sampleCss.copyW");
|
||||||
height: 24px;
|
height: 24px;
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
padding: 0 6px;
|
padding: 0;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
|||||||
Reference in new issue
Block a user