feat(题目): 手机上「运行例子 / 提交」固定在屏幕底部,看着题目也能直接交
- 两个 44px 高的大按钮固定在底部,三个页签下都在;原来它们在「代码」页签顶上, 和语言、更多挤成两行,看着题面想交一次还得先切回代码页签 - 「代码」页签的编辑器一直挂着(原来切走就卸载):没打开过代码页签也能交,撤销历史也不丢 - 编辑器高度让出底部那条,最后几行不再被按钮盖住 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0c2ff68e9b
commit
9110c678fc
6 files changed
+96
-10
No files matched your search
@@ -269,8 +269,9 @@ const goEdit = () => {
|
|||||||
@update:value="changeLanguage"
|
@update:value="changeLanguage"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- 手机上「运行例子」「提交」在屏幕底部那条(MobileActionBar),三个页签都在 -->
|
||||||
<n-button
|
<n-button
|
||||||
v-if="canRunSamples"
|
v-if="canRunSamples && isDesktop"
|
||||||
:size="buttonSize"
|
:size="buttonSize"
|
||||||
:loading="samplesRunning"
|
:loading="samplesRunning"
|
||||||
:disabled="!codeStore.code.value.trim()"
|
:disabled="!codeStore.code.value.trim()"
|
||||||
@@ -281,7 +282,7 @@ const goEdit = () => {
|
|||||||
|
|
||||||
<SubmitFlowchart v-if="codeStore.code.language === 'Flowchart'" />
|
<SubmitFlowchart v-if="codeStore.code.language === 'Flowchart'" />
|
||||||
|
|
||||||
<SubmitCode v-else />
|
<SubmitCode v-else-if="isDesktop" />
|
||||||
|
|
||||||
<n-button
|
<n-button
|
||||||
v-if="isDesktop && userStore.isTeacherOrAbove"
|
v-if="isDesktop && userStore.isTeacherOrAbove"
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useThemeVars } from "naive-ui"
|
||||||
|
import { storeToRefs } from "pinia"
|
||||||
|
import { useCodeStore } from "oj/store/code"
|
||||||
|
import { useProblemStore } from "oj/store/problem"
|
||||||
|
import { useSubmissionStore } from "oj/store/submission"
|
||||||
|
import SubmitCode from "./SubmitCode.vue"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手机上屏幕底部固定的「运行例子 / 提交」(设计文档 5.6)。原来这两个按钮在「代码」页签
|
||||||
|
* 顶上,和语言、更多挤成两行;看着题面想交一次,还得先切回代码页签。
|
||||||
|
* 提交状态、代码都在 store 里,编辑器没挂着也能交。
|
||||||
|
*/
|
||||||
|
const theme = useThemeVars()
|
||||||
|
const codeStore = useCodeStore()
|
||||||
|
const submissionStore = useSubmissionStore()
|
||||||
|
const { samplesRunning } = submissionStore.trial
|
||||||
|
const { problem } = storeToRefs(useProblemStore())
|
||||||
|
|
||||||
|
// 和工具栏同一个条件:Judge0 跑不了 SQL,流程图没得跑,没有例子的题不给
|
||||||
|
const canRunSamples = computed(
|
||||||
|
() =>
|
||||||
|
!!problem.value?.samples.length &&
|
||||||
|
codeStore.code.language !== "Flowchart" &&
|
||||||
|
codeStore.code.language !== "SQL",
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="mobile-actions">
|
||||||
|
<n-button
|
||||||
|
v-if="canRunSamples"
|
||||||
|
size="large"
|
||||||
|
class="action"
|
||||||
|
:loading="samplesRunning"
|
||||||
|
:disabled="!codeStore.code.value.trim()"
|
||||||
|
@click="submissionStore.runSamples()"
|
||||||
|
>
|
||||||
|
运行例子
|
||||||
|
</n-button>
|
||||||
|
<div class="action">
|
||||||
|
<SubmitCode size="large" block />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.mobile-actions {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 10;
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 8px 16px calc(8px + env(safe-area-inset-bottom));
|
||||||
|
background-color: v-bind("theme.bodyColor");
|
||||||
|
border-top: 1px solid rgba(128, 128, 128, 0.2);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-actions :deep(.n-button) {
|
||||||
|
height: 44px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -98,9 +98,10 @@ const storageKey = computed(() =>
|
|||||||
: `problem_${problem.value!._id}_contest_${contestID}_lang_${codeStore.code.language}`,
|
: `problem_${problem.value!._id}_contest_${contestID}_lang_${codeStore.code.language}`,
|
||||||
)
|
)
|
||||||
|
|
||||||
// 协作条 40px + 和工具栏之间的 12px 间距,要从编辑器里让出来,不然页面底下多出一截滚动条
|
// 协作条 40px + 和工具栏之间的 12px 间距,要从编辑器里让出来,不然页面底下多出一截滚动条。
|
||||||
|
// 手机上:顶栏两行 + 课堂条 + 页签 + 工具栏约 236px,底部还有「运行例子 / 提交」那条约 64px
|
||||||
const editorHeight = computed(() => {
|
const editorHeight = computed(() => {
|
||||||
const base = isDesktop.value ? 133 : 172
|
const base = isDesktop.value ? 133 : 300
|
||||||
return `calc(100vh - ${base + (teacherCollab.value ? 52 : 0)}px)`
|
return `calc(100vh - ${base + (teacherCollab.value ? 52 : 0)}px)`
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ import { useUserStore } from "shared/store/user"
|
|||||||
* 这个组件在编辑器的工具栏里,编辑器卸载时它也跟着卸载,所以什么都不能只存在这里。
|
* 这个组件在编辑器的工具栏里,编辑器卸载时它也跟着卸载,所以什么都不能只存在这里。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/** 手机底部那条操作栏里用大号、占满半宽 */
|
||||||
|
const props = defineProps<{ size?: "small" | "medium" | "large"; block?: boolean }>()
|
||||||
|
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const codeStore = useCodeStore()
|
const codeStore = useCodeStore()
|
||||||
const submissionStore = useSubmissionStore()
|
const submissionStore = useSubmissionStore()
|
||||||
@@ -41,7 +44,8 @@ function submit() {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<n-button
|
<n-button
|
||||||
:size="isDesktop ? 'medium' : 'small'"
|
:size="props.size ?? (isDesktop ? 'medium' : 'small')"
|
||||||
|
:block="props.block"
|
||||||
type="primary"
|
type="primary"
|
||||||
:disabled="buttonState.disabled"
|
:disabled="buttonState.disabled"
|
||||||
@click="submit"
|
@click="submit"
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ const ProblemEditor = defineAsyncComponent(loadProblemEditor)
|
|||||||
const ProblemContent = defineAsyncComponent(() => import("./components/ProblemContent.vue"))
|
const ProblemContent = defineAsyncComponent(() => import("./components/ProblemContent.vue"))
|
||||||
const ProblemDrawer = defineAsyncComponent(() => import("./components/ProblemDrawer.vue"))
|
const ProblemDrawer = defineAsyncComponent(() => import("./components/ProblemDrawer.vue"))
|
||||||
const ResultPane = defineAsyncComponent(() => import("./components/ResultPane.vue"))
|
const ResultPane = defineAsyncComponent(() => import("./components/ResultPane.vue"))
|
||||||
|
const MobileActionBar = defineAsyncComponent(() => import("./components/MobileActionBar.vue"))
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
problemID: string
|
problemID: string
|
||||||
@@ -256,14 +257,18 @@ onBeforeUnmount(() => {
|
|||||||
</template>
|
</template>
|
||||||
</n-split>
|
</n-split>
|
||||||
|
|
||||||
<!-- Mobile -->
|
<!-- Mobile:底部固定「运行例子 / 提交」,内容底下留出它的高度 -->
|
||||||
<template v-else>
|
<div v-else class="mobile">
|
||||||
<ContextBar />
|
<ContextBar />
|
||||||
<n-tabs v-model:value="currentTab" type="segment">
|
<n-tabs v-model:value="currentTab" type="segment">
|
||||||
<n-tab-pane name="content" tab="题目">
|
<n-tab-pane name="content" tab="题目">
|
||||||
<ProblemContent />
|
<ProblemContent />
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
<n-tab-pane name="editor" tab="代码">
|
<!--
|
||||||
|
编辑器一直挂着(show,不是默认的 if):底部的「提交」在三个页签下都能点,
|
||||||
|
没切到过「代码」页签的话草稿就没读进来、按钮是灰的;切走就卸载还会丢掉撤销历史
|
||||||
|
-->
|
||||||
|
<n-tab-pane name="editor" tab="代码" display-directive="show">
|
||||||
<ProblemEditor />
|
<ProblemEditor />
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
<n-tab-pane name="result" :tab="resultTab" display-directive="show:lazy">
|
<n-tab-pane name="result" :tab="resultTab" display-directive="show:lazy">
|
||||||
@@ -276,12 +281,17 @@ onBeforeUnmount(() => {
|
|||||||
</template>
|
</template>
|
||||||
</n-tabs>
|
</n-tabs>
|
||||||
<ProblemDrawer v-model="drawer" />
|
<ProblemDrawer v-model="drawer" />
|
||||||
</template>
|
<MobileActionBar />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<n-empty v-else :description="errMsg"></n-empty>
|
<n-empty v-else :description="errMsg"></n-empty>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.mobile {
|
||||||
|
padding-bottom: calc(72px + env(safe-area-inset-bottom));
|
||||||
|
}
|
||||||
|
|
||||||
.left-pane {
|
.left-pane {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# OJ2 设计文档:题目详情页重设计
|
# OJ2 设计文档:题目详情页重设计
|
||||||
|
|
||||||
日期:2026-09-28
|
日期:2026-09-28
|
||||||
状态:**设计已定,第 14 节的 1–5 步和第 7 节已实施**(剩第 6 步:协作的几处规则、手机版)。效果图在 claude.ai 的私有画布「题目详情页重设计」里(「机房(方案 B)」
|
状态:**第 14 节的 6 步和第 7 节都已实施**(2026-09-28)。和本文有出入的地方:手机上「⋯」只收了统计 / 点评 / 我的提交,去自测猫、复制、重置还在「代码」页签的「更多」里;协作中上下文条的跳转没有改成开新标签(老师不在课堂名单里,题库入口下这一条本来就不出现)。效果图在 claude.ai 的私有画布「题目详情页重设计」里(「机房(方案 B)」
|
||||||
那一页是定稿,「初稿 1280×800」那一页已作废,只留着对照)。效果图表达不了的规则都写在这里,
|
那一页是定稿,「初稿 1280×800」那一页已作废,只留着对照)。效果图表达不了的规则都写在这里,
|
||||||
**实施时以本文为准**。
|
**实施时以本文为准**。
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user