style(出题页): 左边写的地方放大、右边「学生看到的样子」缩小,右边能收起来专心写
Deploy / deploy (push) Canceled after 0s

编程题、SQL 题两张出题页一起改:两栏 3 : 2,右栏头上「收起 ›」,收起后剩一条竖着的「学生看到的样子」点了再展开;收没收记在本机

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-09 21:03:50 +08:00
1 parent 151bbdcb50
commit dd10e64b38
5 files changed
+115 -8

No files matched your search

+17 -4
View File
@@ -14,6 +14,8 @@ import { errorCode, errorMessage, isApiError } from "utils/api"
import { DIFFICULTY, STORAGE_KEY } from "utils/constants" import { DIFFICULTY, STORAGE_KEY } from "utils/constants"
import { createZipBlob, getTagColor, unique } from "utils/functions" import { createZipBlob, getTagColor, unique } from "utils/functions"
import type { AdminProblem, BlankProblem, SqlConfig, Tag, Testcase } from "utils/types" import type { AdminProblem, BlankProblem, SqlConfig, Tag, Testcase } from "utils/types"
import PreviewStrip from "./components/PreviewStrip.vue"
import { usePreviewCollapsed } from "./components/previewCollapse"
import { import {
createContestProblem, createContestProblem,
createProblem, createProblem,
@@ -53,6 +55,7 @@ const props = defineProps<{
const message = useMessage() const message = useMessage()
const router = useRouter() const router = useRouter()
const theme = useThemeVars() const theme = useThemeVars()
const previewCollapsed = usePreviewCollapsed()
const isDark = useDark() const isDark = useDark()
const isCreate = computed(() => !props.problemID) const isCreate = computed(() => !props.problemID)
@@ -445,7 +448,7 @@ onMounted(async () => {
</template> </template>
</PageHeader> </PageHeader>
<div v-if="ready" class="sqlEditor"> <div v-if="ready" class="sqlEditor" :class="{ collapsed: previewCollapsed }">
<!-- 左:写 --> <!-- 左:写 -->
<div class="write"> <div class="write">
<section class="card"> <section class="card">
@@ -634,7 +637,7 @@ onMounted(async () => {
</div> </div>
<!-- 右:学生看到的 --> <!-- 右:学生看到的 -->
<aside class="preview" aria-label="学生看到的样子"> <aside v-show="!previewCollapsed" class="preview" aria-label="学生看到的样子">
<div class="previewHead"> <div class="previewHead">
<b>学生看到的样子</b> <b>学生看到的样子</b>
<div class="grow"></div> <div class="grow"></div>
@@ -643,6 +646,9 @@ onMounted(async () => {
第 {{ i + 1 }} 组<span v-if="statusOf(g) === 'error'" class="error"> ✗</span> 第 {{ i + 1 }} 组<span v-if="statusOf(g) === 'error'" class="error"> ✗</span>
</n-radio-button> </n-radio-button>
</n-radio-group> </n-radio-group>
<n-button size="small" quaternary title="收起,专心写左边" @click="previewCollapsed = true">
收起 ›
</n-button>
</div> </div>
<div class="page"> <div class="page">
<div class="titleRow"> <div class="titleRow">
@@ -703,6 +709,7 @@ onMounted(async () => {
</template> </template>
</div> </div>
</aside> </aside>
<PreviewStrip v-if="previewCollapsed" @expand="previewCollapsed = false" />
</div> </div>
</template> </template>
@@ -711,16 +718,22 @@ onMounted(async () => {
font-size: 12px; font-size: 12px;
} }
/* 写的一边是主角,看的一边小一点(用户 2026-10-09:编辑区域要大);右栏可以收起来 */
.sqlEditor { .sqlEditor {
display: grid; display: grid;
grid-template-columns: minmax(0, 560px) minmax(0, 1fr); grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
gap: 20px; gap: 20px;
align-items: start; align-items: start;
} }
.sqlEditor.collapsed {
grid-template-columns: minmax(0, 1fr) 36px;
}
/* 窄屏(老师的小笔记本)放不下两栏:预览挪到下面 */ /* 窄屏(老师的小笔记本)放不下两栏:预览挪到下面 */
@media (max-width: 1180px) { @media (max-width: 1180px) {
.sqlEditor { .sqlEditor,
.sqlEditor.collapsed {
grid-template-columns: minmax(0, 1fr); grid-template-columns: minmax(0, 1fr);
} }
} }
@@ -39,7 +39,7 @@ const props = defineProps<{
}>() }>()
const tab = defineModel<PreviewTab>("tab", { required: true }) const tab = defineModel<PreviewTab>("tab", { required: true })
const emit = defineEmits<{ renderState: [ok: boolean] }>() const emit = defineEmits<{ renderState: [ok: boolean]; collapse: [] }>()
const theme = useThemeVars() const theme = useThemeVars()
const isDark = useDark() const isDark = useDark()
@@ -117,6 +117,9 @@ onMounted(() => nextTick(render))
流程图 流程图
</n-radio-button> </n-radio-button>
</n-radio-group> </n-radio-group>
<n-button size="small" quaternary title="收起,专心写左边" @click="emit('collapse')">
收起 ›
</n-button>
</div> </div>
<!-- 题面 --> <!-- 题面 -->
@@ -0,0 +1,71 @@
<script setup lang="ts">
import { useThemeVars } from "naive-ui"
/**
* 出题页右栏「学生看到的样子」收起来之后剩下的那一条:竖着贴在右边,点一下展开。
* 收不收记在本机(usePreviewCollapsed),下次打开出题页照旧
*/
defineEmits<{ expand: [] }>()
const theme = useThemeVars()
</script>
<template>
<button class="strip" title="展开「学生看到的样子」" @click="$emit('expand')">
<span class="arrow">‹</span>
<span class="text">学生看到的样子</span>
</button>
</template>
<style scoped>
.strip {
position: sticky;
top: 12px;
height: 220px;
padding: 10px 0;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
border: 1px solid v-bind("theme.borderColor");
border-radius: 6px;
background: v-bind("theme.cardColor");
color: v-bind("theme.textColor2");
font: inherit;
font-size: 13px;
cursor: pointer;
}
.strip:hover {
color: v-bind("theme.primaryColor");
border-color: v-bind("theme.primaryColor");
}
.arrow {
font-size: 16px;
line-height: 1;
}
.text {
writing-mode: vertical-rl;
letter-spacing: 2px;
}
/* 窄屏预览本来就在下面,收起后是横着的一条 */
@media (max-width: 1180px) {
.strip {
position: static;
height: 40px;
flex-direction: row;
justify-content: center;
}
.arrow {
transform: rotate(-90deg);
}
.text {
writing-mode: horizontal-tb;
}
}
</style>
@@ -0,0 +1,7 @@
/**
* 出题页右栏「学生看到的样子」收没收起来。两张出题页(编程题、SQL 题)共用一个开关,
* 记在本机:老师想专心写的时候收起来,下次打开还是收着的
*/
export function usePreviewCollapsed() {
return useStorage("oj2:admin-problem-preview-collapsed", false)
}
+16 -3
View File
@@ -22,6 +22,8 @@ import {
import AstRulesEditor from "./components/AstRulesEditor.vue" import AstRulesEditor from "./components/AstRulesEditor.vue"
import CaseTable from "./components/CaseTable.vue" import CaseTable from "./components/CaseTable.vue"
import CodePreview from "./components/CodePreview.vue" import CodePreview from "./components/CodePreview.vue"
import PreviewStrip from "./components/PreviewStrip.vue"
import { usePreviewCollapsed } from "./components/previewCollapse"
import { import {
caseFiles, caseFiles,
caseStatus, caseStatus,
@@ -458,6 +460,7 @@ const open = reactive({
more: false, more: false,
}) })
const preview = ref<PreviewTab>("statement") const preview = ref<PreviewTab>("statement")
const previewCollapsed = usePreviewCollapsed()
watch( watch(
() => open.write, () => open.write,
@@ -842,7 +845,7 @@ onMounted(async () => {
</template> </template>
</PageHeader> </PageHeader>
<div v-if="ready" class="codeEditor"> <div v-if="ready" class="codeEditor" :class="{ collapsed: previewCollapsed }">
<!-- 左:写 --> <!-- 左:写 -->
<div class="write"> <div class="write">
<div v-if="draftRestored" class="notice">上次没保存的草稿已经恢复</div> <div v-if="draftRestored" class="notice">上次没保存的草稿已经恢复</div>
@@ -1273,7 +1276,9 @@ onMounted(async () => {
</div> </div>
<!-- 右:学生看到的 --> <!-- 右:学生看到的 -->
<!-- 收起时用 v-show 不卸载:流程图渲染成没成功(mermaidOk)还要接着报给保存按钮 -->
<CodePreview <CodePreview
v-show="!previewCollapsed"
v-model:tab="preview" v-model:tab="preview"
:display-id="form._id" :display-id="form._id"
:title="form.title" :title="form.title"
@@ -1293,7 +1298,9 @@ onMounted(async () => {
:mermaid-code="form.mermaidCode" :mermaid-code="form.mermaidCode"
:flowchart-hint="form.flowchartHint" :flowchart-hint="form.flowchartHint"
@render-state="mermaidOk = $event" @render-state="mermaidOk = $event"
@collapse="previewCollapsed = true"
/> />
<PreviewStrip v-if="previewCollapsed" @expand="previewCollapsed = false" />
</div> </div>
</template> </template>
@@ -1314,16 +1321,22 @@ onMounted(async () => {
font-size: 12px; font-size: 12px;
} }
/* 写的一边是主角,看的一边小一点(用户 2026-10-09:编辑区域要大);右栏可以收起来 */
.codeEditor { .codeEditor {
display: grid; display: grid;
grid-template-columns: minmax(0, 600px) minmax(0, 1fr); grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
gap: 20px; gap: 20px;
align-items: start; align-items: start;
} }
.codeEditor.collapsed {
grid-template-columns: minmax(0, 1fr) 36px;
}
/* 窄屏(老师的小笔记本)放不下两栏:预览挪到下面 */ /* 窄屏(老师的小笔记本)放不下两栏:预览挪到下面 */
@media (max-width: 1180px) { @media (max-width: 1180px) {
.codeEditor { .codeEditor,
.codeEditor.collapsed {
grid-template-columns: minmax(0, 1fr); grid-template-columns: minmax(0, 1fr);
} }
} }