Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cea2c7f102 | ||
|
|
a5d5c39411 |
No files matched your search
@@ -96,6 +96,8 @@ const { query, clearQuery } = usePagination<ProblemQuery>({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const counts = ref<{ all: number; visible: number; hidden: number } | null>(null)
|
const counts = ref<{ all: number; visible: number; hidden: number } | null>(null)
|
||||||
|
// 手机上后台左边还有一列图标,三个按钮放不下、「藏着的」被切掉:换小号,再不够就横着滑
|
||||||
|
const narrow = useMediaQuery("(max-width: 640px)")
|
||||||
const visibleTabs = computed(() => [
|
const visibleTabs = computed(() => [
|
||||||
{ value: "" as const, label: "全部", count: counts.value?.all },
|
{ value: "" as const, label: "全部", count: counts.value?.all },
|
||||||
{ value: "1" as const, label: "学生看得到", count: counts.value?.visible },
|
{ value: "1" as const, label: "学生看得到", count: counts.value?.visible },
|
||||||
@@ -324,10 +326,11 @@ watch(() => [query.page, query.limit, query.author, query.kind, query.visible],
|
|||||||
<n-text depth="3">出题人</n-text>
|
<n-text depth="3">出题人</n-text>
|
||||||
<AuthorSelect v-model:value="query.author" all />
|
<AuthorSelect v-model:value="query.author" all />
|
||||||
</n-flex>
|
</n-flex>
|
||||||
<n-button-group v-if="!props.contestID">
|
<n-button-group v-if="!props.contestID" class="visible-tabs">
|
||||||
<n-button
|
<n-button
|
||||||
v-for="tab in visibleTabs"
|
v-for="tab in visibleTabs"
|
||||||
:key="tab.value"
|
:key="tab.value"
|
||||||
|
:size="narrow ? 'small' : 'medium'"
|
||||||
:type="query.visible === tab.value ? 'primary' : 'default'"
|
:type="query.visible === tab.value ? 'primary' : 'default'"
|
||||||
:secondary="query.visible === tab.value"
|
:secondary="query.visible === tab.value"
|
||||||
@click="query.visible = tab.value"
|
@click="query.visible = tab.value"
|
||||||
@@ -388,6 +391,11 @@ watch(() => [query.page, query.limit, query.author, query.kind, query.visible],
|
|||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.visible-tabs {
|
||||||
|
max-width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.tab-count {
|
.tab-count {
|
||||||
margin-left: 6px;
|
margin-left: 6px;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
|
|||||||
@@ -8,6 +8,20 @@ import storage from "utils/storage"
|
|||||||
import App from "./App.vue"
|
import App from "./App.vue"
|
||||||
import { admins, ojs } from "./routes"
|
import { admins, ojs } from "./routes"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* iPhone 上点进字号小于 16px 的输入框(Naive 的输入框是 14px、代码编辑器 15px),
|
||||||
|
* Safari 会自动把整页放大,而且退出输入也不缩回去,得学生自己双指缩。
|
||||||
|
* maximum-scale=1 能关掉这个自动放大;iOS 10 起照样允许双指缩放,所以只给 iOS 加 ——
|
||||||
|
* 安卓上这么写会真的禁掉双指放大,看题面图片、代码就没法放大了。
|
||||||
|
*/
|
||||||
|
const isIOS =
|
||||||
|
/iPhone|iPad|iPod/.test(navigator.userAgent) ||
|
||||||
|
(navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1)
|
||||||
|
if (isIOS) {
|
||||||
|
const viewport = document.querySelector('meta[name="viewport"]')
|
||||||
|
viewport?.setAttribute("content", "width=device-width, initial-scale=1.0, maximum-scale=1")
|
||||||
|
}
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(),
|
history: createWebHistory(),
|
||||||
routes: [ojs, admins],
|
routes: [ojs, admins],
|
||||||
|
|||||||
@@ -20,7 +20,12 @@ const cells = computed(() => {
|
|||||||
const percent = r.solved ? Math.round((r.firstTry / r.solved) * 100) : 0
|
const percent = r.solved ? Math.round((r.firstTry / r.solved) * 100) : 0
|
||||||
const firstSub = `一次就对 · 占 ${percent}%`
|
const firstSub = `一次就对 · 占 ${percent}%`
|
||||||
const list: Cell[] = [
|
const list: Cell[] = [
|
||||||
{ big: r.solved, unit: "道", sub: c ? `做对 · 班上中间那位 ${c.medianSolved} 道` : "做对" },
|
// 数和「道」之间用不换行空格:手机上两列挤,单个「道」会被甩到下一行
|
||||||
|
{
|
||||||
|
big: r.solved,
|
||||||
|
unit: "道",
|
||||||
|
sub: c ? `做对 · 班上中间那位 ${c.medianSolved}\u00a0道` : "做对",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
big: r.firstTry,
|
big: r.firstTry,
|
||||||
unit: "道",
|
unit: "道",
|
||||||
|
|||||||
@@ -80,7 +80,8 @@ const shownRows = computed(() =>
|
|||||||
const weeks = computed(() => {
|
const weeks = computed(() => {
|
||||||
const from = Math.max(0, props.pk.weeks.length - 8)
|
const from = Math.max(0, props.pk.weeks.length - 8)
|
||||||
const list = props.pk.weeks.slice(from).map((week, k) => ({
|
const list = props.pk.weeks.slice(from).map((week, k) => ({
|
||||||
label: parseTime(week, "M月D日"),
|
// 手机上八根柱挤在一行,「10月12日」只能用 10px 才放得下,太小;改短写法换大一号字
|
||||||
|
label: parseTime(week, props.compact ? "M/D" : "M月D日"),
|
||||||
values: [left.value.weekly[from + k] ?? 0, right.value.weekly[from + k] ?? 0],
|
values: [left.value.weekly[from + k] ?? 0, right.value.weekly[from + k] ?? 0],
|
||||||
}))
|
}))
|
||||||
const max = Math.max(1, ...list.flatMap((week) => week.values))
|
const max = Math.max(1, ...list.flatMap((week) => week.values))
|
||||||
@@ -779,6 +780,6 @@ const tab = ref<"weekly" | "dist">("weekly")
|
|||||||
}
|
}
|
||||||
|
|
||||||
.compact .axis {
|
.compact .axis {
|
||||||
font-size: 10px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -97,10 +97,16 @@ const meBackground = computed(
|
|||||||
`linear-gradient(${success.value.background}, ${success.value.background}), ${theme.value.cardColor}`,
|
`linear-gradient(${success.value.background}, ${success.value.background}), ${theme.value.cardColor}`,
|
||||||
)
|
)
|
||||||
const danger = computed(() => tone("error"))
|
const danger = computed(() => tone("error"))
|
||||||
const columns = computed(
|
// 手机上照桌面的列宽,名字 + 做对 + 用时就占满一屏,每题的格子全在屏幕外,看不出能往右滑。
|
||||||
() =>
|
// 收窄到第一两道题的格子露出半截
|
||||||
`40px ${running.value ? "36px " : ""}170px 48px 80px repeat(${problems.value.length}, 64px)`,
|
const narrow = useMediaQuery("(max-width: 640px)")
|
||||||
)
|
const columns = computed(() => {
|
||||||
|
const move = running.value ? "36px " : ""
|
||||||
|
const count = problems.value.length
|
||||||
|
return narrow.value
|
||||||
|
? `28px ${move}112px 36px 70px repeat(${count}, 52px)`
|
||||||
|
: `40px ${move}170px 48px 80px repeat(${count}, 64px)`
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -137,9 +143,13 @@ const columns = computed(
|
|||||||
</svg>
|
</svg>
|
||||||
<template v-if="podium[place - 1]">
|
<template v-if="podium[place - 1]">
|
||||||
<UserName class="podium-name" :username="podium[place - 1]!.username" />
|
<UserName class="podium-name" :username="podium[place - 1]!.username" />
|
||||||
<span class="muted small">
|
<span class="muted small podium-meta">
|
||||||
{{ classLabel(podium[place - 1]!.className) }} · {{ podium[place - 1]!.solved }} 道 ·
|
<span>{{ classLabel(podium[place - 1]!.className) }}</span
|
||||||
{{ secondsToDuration(podium[place - 1]!.totalTime) }}
|
><span class="sep"> · </span
|
||||||
|
><span
|
||||||
|
>{{ podium[place - 1]!.solved }} 道 ·
|
||||||
|
{{ secondsToDuration(podium[place - 1]!.totalTime) }}</span
|
||||||
|
>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<div class="block">{{ place }}</div>
|
<div class="block">{{ place }}</div>
|
||||||
@@ -534,4 +544,47 @@ const columns = computed(
|
|||||||
.empty {
|
.empty {
|
||||||
padding: 32px var(--oj-pad-x);
|
padding: 32px var(--oj-pad-x);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 手机:三级台阶一共 646px 宽,原样摆第一名就被挤到屏幕外了。三块平分一行,
|
||||||
|
名字截断、班级和用时折成两行 */
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.podium-row {
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.podium {
|
||||||
|
width: 100%;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step {
|
||||||
|
flex: 1 1 0;
|
||||||
|
width: auto;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.podium-name,
|
||||||
|
.p1 .podium-name {
|
||||||
|
font-size: 15px;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.podium-meta {
|
||||||
|
font-size: var(--oj-fs-meta);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.podium-meta > span {
|
||||||
|
display: block;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.podium-meta > .sep {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mine {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -75,6 +75,14 @@ function inline(text: string) {
|
|||||||
.body :deep(.oj-markdown th) {
|
.body :deep(.oj-markdown th) {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
/* 360 宽的安卓机上四列表差三十来像素,最后一列被切掉、得横着滑才看得到:格子收窄一点 */
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.body :deep(.oj-markdown th),
|
||||||
|
.body :deep(.oj-markdown td) {
|
||||||
|
min-width: 3.5em;
|
||||||
|
padding: 6px 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
.goals {
|
.goals {
|
||||||
margin: 4px 0 8px;
|
margin: 4px 0 8px;
|
||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { useCodeStore } from "oj/store/code"
|
|||||||
import { useProblemStore } from "oj/store/problem"
|
import { useProblemStore } from "oj/store/problem"
|
||||||
import { useSubmissionStore } from "oj/store/submission"
|
import { useSubmissionStore } from "oj/store/submission"
|
||||||
import SubmitCode from "./SubmitCode.vue"
|
import SubmitCode from "./SubmitCode.vue"
|
||||||
import { editorFocused } from "../utils/symbolKeys"
|
import { typing } from "../utils/symbolKeys"
|
||||||
|
|
||||||
const SubmitFlowchart = defineAsyncComponent(() => import("./SubmitFlowchart.vue"))
|
const SubmitFlowchart = defineAsyncComponent(() => import("./SubmitFlowchart.vue"))
|
||||||
|
|
||||||
@@ -30,8 +30,9 @@ const canRunSamples = computed(
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- 写代码时(键盘弹着)让位给符号键:两条都贴底会叠在一起,安卓上还会被键盘顶上来 -->
|
<!-- 写代码时(键盘弹着)让位给符号键:两条都贴底会叠在一起,安卓上还会被键盘顶上来。
|
||||||
<div v-show="!editorFocused" class="mobile-actions">
|
按返回键收了键盘、编辑器还有焦点时要回来,所以看 typing 不看焦点 -->
|
||||||
|
<div v-show="!typing" class="mobile-actions">
|
||||||
<n-button
|
<n-button
|
||||||
v-if="canRunSamples"
|
v-if="canRunSamples"
|
||||||
size="large"
|
size="large"
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useThemeVars } from "naive-ui"
|
import { useThemeVars } from "naive-ui"
|
||||||
import { useCodeStore } from "oj/store/code"
|
import { useCodeStore } from "oj/store/code"
|
||||||
import { editorFocused, SYMBOL_KEYS, typeSymbol } from "../utils/symbolKeys"
|
import {
|
||||||
|
editorFocused,
|
||||||
|
measureKeyboard,
|
||||||
|
SYMBOL_KEYS,
|
||||||
|
typeSymbol,
|
||||||
|
typing,
|
||||||
|
} from "../utils/symbolKeys"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 手机上贴着键盘顶的一排符号键,编辑器有焦点(键盘弹着)时才出来。
|
* 手机上贴着键盘顶的一排符号键,编辑器有焦点(键盘弹着)时才出来。
|
||||||
@@ -29,9 +35,19 @@ function place() {
|
|||||||
top.value = viewport ? viewport.offsetTop + viewport.height - BAR_HEIGHT : null
|
top.value = viewport ? viewport.offsetTop + viewport.height - BAR_HEIGHT : null
|
||||||
}
|
}
|
||||||
|
|
||||||
useEventListener(() => window.visualViewport, "resize", place)
|
// 键盘弹起之前就得量出「没键盘时多高」,所以挂上就量
|
||||||
|
onMounted(measureKeyboard)
|
||||||
|
useEventListener("resize", measureKeyboard)
|
||||||
|
useEventListener(
|
||||||
|
() => window.visualViewport,
|
||||||
|
"resize",
|
||||||
|
() => {
|
||||||
|
measureKeyboard()
|
||||||
|
place()
|
||||||
|
},
|
||||||
|
)
|
||||||
useEventListener(() => window.visualViewport, "scroll", place)
|
useEventListener(() => window.visualViewport, "scroll", place)
|
||||||
watch(editorFocused, (focused) => focused && place(), { immediate: true })
|
watch(typing, (on) => on && place(), { immediate: true })
|
||||||
// 键盘弹着时点了别处换题、离开页面,编辑器直接卸载,不一定等得到失焦
|
// 键盘弹着时点了别处换题、离开页面,编辑器直接卸载,不一定等得到失焦
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
editorFocused.value = false
|
editorFocused.value = false
|
||||||
@@ -40,7 +56,7 @@ onBeforeUnmount(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-if="editorFocused && keys.length"
|
v-if="typing && keys.length"
|
||||||
class="symbol-keys"
|
class="symbol-keys"
|
||||||
:style="top === null ? { bottom: '0px' } : { top: `${top}px` }"
|
:style="top === null ? { bottom: '0px' } : { top: `${top}px` }"
|
||||||
role="toolbar"
|
role="toolbar"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { insertBracket } from "@codemirror/autocomplete"
|
import { insertBracket } from "@codemirror/autocomplete"
|
||||||
import { EditorView } from "@codemirror/view"
|
import { EditorView } from "@codemirror/view"
|
||||||
import { ref } from "vue"
|
import { computed, ref } from "vue"
|
||||||
import { currentEditorView } from "./errorMark"
|
import { currentEditorView } from "./errorMark"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -8,7 +8,7 @@ import { currentEditorView } from "./errorMark"
|
|||||||
* 一行 print(int(input())) 来回切七八次。
|
* 一行 print(int(input())) 来回切七八次。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** 题目页主编辑器有没有焦点 —— 有焦点就是键盘弹着,这时才摆符号键 */
|
/** 题目页主编辑器有没有焦点 */
|
||||||
export const editorFocused = ref(false)
|
export const editorFocused = ref(false)
|
||||||
|
|
||||||
export const editorFocusTracker = EditorView.focusChangeEffect.of((_state, focusing) => {
|
export const editorFocusTracker = EditorView.focusChangeEffect.of((_state, focusing) => {
|
||||||
@@ -16,6 +16,35 @@ export const editorFocusTracker = EditorView.focusChangeEffect.of((_state, focus
|
|||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 软键盘是不是弹着。光看焦点不够:安卓按返回键收键盘,编辑器**不失焦**,
|
||||||
|
* 符号键就一直贴在屏幕底下,「运行例子 / 提交」也不回来,学生找不到地方交。
|
||||||
|
*
|
||||||
|
* 键盘弹起时可视视口变矮(iOS 和 Chrome 108+ 布局视口不变;更老的安卓连
|
||||||
|
* innerHeight 一起变矮),所以和这个宽度下见过的最高值比。宽度变了(转屏)重新量。
|
||||||
|
* 乘 scale 是为了双指放大时别误判成键盘弹着。
|
||||||
|
*/
|
||||||
|
const keyboardUp = ref(false)
|
||||||
|
let baseline = 0
|
||||||
|
let baselineWidth = 0
|
||||||
|
|
||||||
|
export function measureKeyboard() {
|
||||||
|
const viewport = window.visualViewport
|
||||||
|
if (!viewport) return
|
||||||
|
if (window.innerWidth !== baselineWidth) {
|
||||||
|
baselineWidth = window.innerWidth
|
||||||
|
baseline = 0
|
||||||
|
}
|
||||||
|
const visible = viewport.height * viewport.scale
|
||||||
|
baseline = Math.max(baseline, window.innerHeight, visible)
|
||||||
|
keyboardUp.value = visible < baseline - 120
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 正在打字:编辑器有焦点、键盘也弹着。这时才摆符号键,底栏让位 */
|
||||||
|
export const typing = computed(
|
||||||
|
() => editorFocused.value && (!window.visualViewport || keyboardUp.value),
|
||||||
|
)
|
||||||
|
|
||||||
export interface SymbolKey {
|
export interface SymbolKey {
|
||||||
label: string
|
label: string
|
||||||
/** 插进去的字;不写就是 label */
|
/** 插进去的字;不写就是 label */
|
||||||
|
|||||||
@@ -83,7 +83,12 @@ const spent = computed(() => {
|
|||||||
return `${Math.round(minutes / 1440)} 天`
|
return `${Math.round(minutes / 1440)} 天`
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 两栏放得下(每栏 420 起)才拆;拆完在窄屏上叠起来,就成了两张没有标题的卡片,
|
||||||
|
// 看着像题单分了两组
|
||||||
|
const twoColumns = useMediaQuery("(min-width: 960px)")
|
||||||
|
|
||||||
const columns = computed(() => {
|
const columns = computed(() => {
|
||||||
|
if (!twoColumns.value) return [problems.value]
|
||||||
const half = Math.ceil(problems.value.length / 2)
|
const half = Math.ceil(problems.value.length / 2)
|
||||||
return [problems.value.slice(0, half), problems.value.slice(half)].filter((col) => col.length)
|
return [problems.value.slice(0, half), problems.value.slice(half)].filter((col) => col.length)
|
||||||
})
|
})
|
||||||
@@ -232,7 +237,7 @@ const purple = { color: "#5b3fa8", background: "rgba(122, 95, 208, 0.12)" }
|
|||||||
<ClassView v-if="userStore.isTeacherOrAbove && tab === 'class'" :problem-set-id="set.id" />
|
<ClassView v-if="userStore.isTeacherOrAbove && tab === 'class'" :problem-set-id="set.id" />
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div v-if="problems.length" class="cols">
|
<div v-if="problems.length" class="cols" :class="{ two: twoColumns }">
|
||||||
<section v-for="(col, ci) in columns" :key="ci" class="card list">
|
<section v-for="(col, ci) in columns" :key="ci" class="card list">
|
||||||
<component
|
<component
|
||||||
:is="canOpen ? 'router-link' : 'div'"
|
:is="canOpen ? 'router-link' : 'div'"
|
||||||
@@ -461,11 +466,15 @@ const purple = { color: "#5b3fa8", background: "rgba(122, 95, 208, 0.12)" }
|
|||||||
|
|
||||||
.cols {
|
.cols {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(420px, 1fr));
|
grid-template-columns: minmax(0, 1fr);
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
align-items: start;
|
align-items: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cols.two {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
border: 1px solid v-bind("theme.borderColor");
|
border: 1px solid v-bind("theme.borderColor");
|
||||||
border-radius: var(--oj-radius);
|
border-radius: var(--oj-radius);
|
||||||
@@ -622,10 +631,6 @@ a.prow:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
.cols {
|
|
||||||
grid-template-columns: minmax(0, 1fr);
|
|
||||||
}
|
|
||||||
|
|
||||||
.band {
|
.band {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,13 +53,14 @@ const headline = computed(() => {
|
|||||||
return props.lastListed ? `再做对 ${toPass(me, props.lastListed)} 道就进前 ${props.cap}` : ""
|
return props.lastListed ? `再做对 ${toPass(me, props.lastListed)} 道就进前 ${props.cap}` : ""
|
||||||
if (me.rank === 1) return "你是第一名"
|
if (me.rank === 1) return "你是第一名"
|
||||||
if (me.rank <= 3 && ahead)
|
if (me.rank <= 3 && ahead)
|
||||||
return `你在领奖台上 · 再做对 ${toPass(me, ahead)} 道就是第 ${ahead.rank} 名`
|
// 「第 N 名」用不换行空格连住:手机上这一句正好折行,单个「名」会掉到下一行
|
||||||
|
return `你在领奖台上 · 再做对 ${toPass(me, ahead)} 道就是第\u00a0${ahead.rank}\u00a0名`
|
||||||
if (third) {
|
if (third) {
|
||||||
const need = toPass(me, third)
|
const need = toPass(me, third)
|
||||||
if (need <= 3) return `再做对 ${need} 道,你就上领奖台`
|
if (need <= 3) return `再做对 ${need} 道,你就上领奖台`
|
||||||
}
|
}
|
||||||
if ((me.change ?? 0) >= 3) return `比周一升了 ${me.change} 名`
|
if ((me.change ?? 0) >= 3) return `比周一升了 ${me.change} 名`
|
||||||
if (ahead) return `再做对 ${toPass(me, ahead)} 道就是第 ${ahead.rank} 名`
|
if (ahead) return `再做对 ${toPass(me, ahead)} 道就是第\u00a0${ahead.rank}\u00a0名`
|
||||||
return ""
|
return ""
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -572,5 +572,16 @@ function percent(value: number, total: number) {
|
|||||||
display: block;
|
display: block;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
|
/* 表横着滑,表头别被挤成竖排(「提 / 交」) */
|
||||||
|
.hard th {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.section-title {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
row-gap: 2px;
|
||||||
|
}
|
||||||
|
.section-title b {
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -123,7 +123,7 @@ watch(klass, (value) => {
|
|||||||
clearError()
|
clearError()
|
||||||
if (value) {
|
if (value) {
|
||||||
loadNames(value)
|
loadNames(value)
|
||||||
focusLater(() => searchRef.value)
|
focusSearch()
|
||||||
} else names.value = []
|
} else names.value = []
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -137,6 +137,15 @@ function focusLater(target: () => { focus: () => void } | null | undefined) {
|
|||||||
nextTick(() => target()?.focus())
|
nextTick(() => target()?.focus())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手机上不替学生点搜索框:一聚焦键盘就弹出来,把下面的名单盖掉一半,
|
||||||
|
* 而手机上本来就是直接点名字更快。电脑上照旧聚焦,进来就能打拼音缩写。
|
||||||
|
*/
|
||||||
|
const touchScreen = useMediaQuery("(pointer: coarse)")
|
||||||
|
function focusSearch() {
|
||||||
|
if (!touchScreen.value) focusLater(() => searchRef.value)
|
||||||
|
}
|
||||||
|
|
||||||
function pick(name: string) {
|
function pick(name: string) {
|
||||||
picked.value = name
|
picked.value = name
|
||||||
password.value = ""
|
password.value = ""
|
||||||
@@ -148,7 +157,7 @@ function unpick() {
|
|||||||
picked.value = null
|
picked.value = null
|
||||||
password.value = ""
|
password.value = ""
|
||||||
clearError()
|
clearError()
|
||||||
focusLater(() => searchRef.value)
|
focusSearch()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 搜到只剩一个人,回车就是点他 */
|
/** 搜到只剩一个人,回车就是点他 */
|
||||||
@@ -163,7 +172,8 @@ function switchMode(next: Mode) {
|
|||||||
password.value = ""
|
password.value = ""
|
||||||
query.value = ""
|
query.value = ""
|
||||||
clearError()
|
clearError()
|
||||||
focusLater(() => (next === "user" ? usernameRef.value : searchRef.value))
|
if (next === "user") focusLater(() => usernameRef.value)
|
||||||
|
else focusSearch()
|
||||||
}
|
}
|
||||||
|
|
||||||
function goSignup() {
|
function goSignup() {
|
||||||
@@ -249,7 +259,8 @@ watch(
|
|||||||
query.value = ""
|
query.value = ""
|
||||||
clearError()
|
clearError()
|
||||||
restoreLast()
|
restoreLast()
|
||||||
focusLater(() => (mode.value === "user" ? usernameRef.value : searchRef.value))
|
if (mode.value === "user") focusLater(() => usernameRef.value)
|
||||||
|
else focusSearch()
|
||||||
},
|
},
|
||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -68,12 +68,13 @@ export function filterEmptyValue<T extends Record<string, any>>(object: T): Part
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getTagColor(tag: "Low" | "Mid" | "High" | "简单" | "中等" | "困难") {
|
export function getTagColor(tag: "Low" | "Mid" | "High" | "简单" | "中等" | "困难") {
|
||||||
return <"success" | "info" | "error">{
|
// 中等是橙色,和题目列表、相似题推荐一个颜色(原来这里是蓝,同一道题进了详情页就变色)
|
||||||
|
return <"success" | "warning" | "error">{
|
||||||
Low: "success",
|
Low: "success",
|
||||||
Mid: "info",
|
Mid: "warning",
|
||||||
High: "error",
|
High: "error",
|
||||||
简单: "success",
|
简单: "success",
|
||||||
中等: "info",
|
中等: "warning",
|
||||||
困难: "error",
|
困难: "error",
|
||||||
}[tag]
|
}[tag]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user