diff --git a/apps/web/src/oj/learn/components/ExerciseDebug.vue b/apps/web/src/oj/learn/components/ExerciseDebug.vue
index c553500..8b5a7a1 100644
--- a/apps/web/src/oj/learn/components/ExerciseDebug.vue
+++ b/apps/web/src/oj/learn/components/ExerciseDebug.vue
@@ -101,7 +101,7 @@ function reset() {
{{ idx + 1 }}
-
+
diff --git a/apps/web/src/oj/learn/components/ExerciseFill.vue b/apps/web/src/oj/learn/components/ExerciseFill.vue
index 1b8d305..2e7432a 100644
--- a/apps/web/src/oj/learn/components/ExerciseFill.vue
+++ b/apps/web/src/oj/learn/components/ExerciseFill.vue
@@ -96,6 +96,10 @@ function inputWidth(idx: number): string {
v-else
:value="userInputs[seg.index]"
:disabled="allCorrect"
+ autocapitalize="off"
+ autocorrect="off"
+ autocomplete="off"
+ spellcheck="false"
:style="{
width: inputWidth(seg.index),
fontFamily: 'var(--learn-code-font)',
diff --git a/apps/web/src/oj/learn/components/ExerciseGroup.vue b/apps/web/src/oj/learn/components/ExerciseGroup.vue
index e9d1afa..ebddaaf 100644
--- a/apps/web/src/oj/learn/components/ExerciseGroup.vue
+++ b/apps/web/src/oj/learn/components/ExerciseGroup.vue
@@ -13,12 +13,15 @@ const data = computed(() => props.exercise.data as ExerciseGroupData)
const order = ref([]) // item 的稳定展示顺序(初始乱序)
const placement = ref([]) // placement[itemIdx] = 桶下标,-1 表示在池中
const dragIdx = ref(null)
+// 手机上没有 HTML5 拖放(触屏不触发 dragstart),所以再给一条点选的路:先点项目、再点分组
+const pickedIdx = ref(null)
const submitted = ref(false)
function init() {
order.value = shuffle(data.value.items.map((_, i) => i))
placement.value = Array(data.value.items.length).fill(-1)
dragIdx.value = null
+ pickedIdx.value = null
submitted.value = false
}
@@ -32,6 +35,7 @@ const locked = computed(() => submitted.value && allCorrect.value)
function onDragStart(i: number) {
if (locked.value) return
dragIdx.value = i
+ pickedIdx.value = null
}
function dropTo(bucket: number) {
@@ -41,6 +45,25 @@ function dropTo(bucket: number) {
submitted.value = false
}
+// 手机上分组框不大,点分组时多半点在框里已有的项目上 —— 这时算「放进它所在的分组」,
+// 只有点同一区域里的另一项才是换选
+function onItemClick(i: number) {
+ if (locked.value) return
+ const picked = pickedIdx.value
+ if (picked !== null && picked !== i && placement.value[picked] !== placement.value[i]) {
+ onAreaClick(placement.value[i])
+ return
+ }
+ pickedIdx.value = picked === i ? null : i
+}
+
+function onAreaClick(bucket: number) {
+ if (locked.value || pickedIdx.value === null) return
+ placement.value[pickedIdx.value] = bucket
+ pickedIdx.value = null
+ submitted.value = false
+}
+
const poolItems = computed(() => order.value.filter((i) => placement.value[i] === -1))
function itemsIn(bucket: number): number[] {
return order.value.filter((i) => placement.value[i] === bucket)
@@ -56,6 +79,17 @@ function chipStyle(i: number): Record {
const color =
status === "correct" ? "#18a058" : status === "wrong" ? "#d03050" : "var(--learn-control-line)"
const plain = color === "var(--learn-control-line)"
+ if (pickedIdx.value === i) {
+ return {
+ padding: "5.5px 11.5px",
+ borderRadius: "6px",
+ border: "2px solid #2080f0",
+ background: "rgba(32,128,240,0.08)",
+ cursor: "pointer",
+ userSelect: "none",
+ fontSize: "15px",
+ }
+ }
return {
padding: "6px 12px",
borderRadius: "6px",
@@ -86,7 +120,7 @@ function reset() {
{{ data.question }}
- 把下面的项目拖到对应的分组里,可在分组间拖动调整。
+ 把下面的项目拖到对应的分组里,也可以先点项目、再点分组。
(已全部归类)
@@ -112,6 +147,7 @@ function reset() {
draggable="true"
:style="chipStyle(i)"
@dragstart="onDragStart(i)"
+ @click.stop="onItemClick(i)"
>
{{ data.items[i] }}
@@ -135,6 +171,7 @@ function reset() {
}"
@dragover.prevent
@drop="dropTo(b)"
+ @click="onAreaClick(b)"
>
{{ bucket }}
@@ -146,6 +183,7 @@ function reset() {
draggable="true"
:style="chipStyle(i)"
@dragstart="onDragStart(i)"
+ @click.stop="onItemClick(i)"
>
{{ data.items[i] }}
diff --git a/apps/web/src/oj/learn/components/ExercisePredict.vue b/apps/web/src/oj/learn/components/ExercisePredict.vue
index ee62d9b..def0911 100644
--- a/apps/web/src/oj/learn/components/ExercisePredict.vue
+++ b/apps/web/src/oj/learn/components/ExercisePredict.vue
@@ -81,6 +81,7 @@ function reset() {
:rows="3"
:disabled="submitted && allCorrect"
placeholder="在这里输入程序会打印的内容"
+ :input-props="{ autocapitalize: 'off', autocorrect: 'off', spellcheck: false }"
style="font-family: var(--learn-code-font)"
/>
diff --git a/apps/web/src/oj/learn/components/ExerciseSort.vue b/apps/web/src/oj/learn/components/ExerciseSort.vue
index 871ea63..bd7a9a6 100644
--- a/apps/web/src/oj/learn/components/ExerciseSort.vue
+++ b/apps/web/src/oj/learn/components/ExerciseSort.vue
@@ -24,6 +24,7 @@ function init() {
;[shuffled[0], shuffled[1]] = [shuffled[1], shuffled[0]]
}
lines.value = shuffled
+ pickedIdx.value = null
submitted.value = false
}
@@ -31,19 +32,37 @@ onMounted(init)
watch(() => props.exercise.id, init)
const dragIdx = ref(null)
+// 手机上没有 HTML5 拖放(触屏不触发 dragstart),所以再给一条点选的路:先点一行、再点它该去的位置
+const pickedIdx = ref(null)
function onDragStart(idx: number) {
dragIdx.value = idx
+ pickedIdx.value = null
+}
+
+function moveLine(from: number, to: number) {
+ if (from === to) return
+ const newLines = [...lines.value]
+ const [moved] = newLines.splice(from, 1)
+ newLines.splice(to, 0, moved)
+ lines.value = newLines
+ submitted.value = false
}
function onDrop(targetIdx: number) {
- if (dragIdx.value === null || dragIdx.value === targetIdx) return
- const newLines = [...lines.value]
- const [moved] = newLines.splice(dragIdx.value, 1)
- newLines.splice(targetIdx, 0, moved)
- lines.value = newLines
+ if (dragIdx.value === null) return
+ moveLine(dragIdx.value, targetIdx)
dragIdx.value = null
- submitted.value = false
+}
+
+function onLineClick(idx: number) {
+ if (submitted.value && allCorrect.value) return
+ if (pickedIdx.value === null) {
+ pickedIdx.value = idx
+ return
+ }
+ moveLine(pickedIdx.value, idx)
+ pickedIdx.value = null
}
function lineStatus(idx: number): "correct" | "wrong" | "default" {
@@ -86,19 +105,24 @@ const lineHtml = computed(() => highlightLines(data.value.lines, props
gap: '10px',
padding: '8px 12px',
borderRadius: '6px',
- border: `1.5px ${submitted ? 'solid' : 'dashed'} ${
- lineStatus(idx) === 'correct'
- ? '#18a058'
- : lineStatus(idx) === 'wrong'
- ? '#d03050'
- : 'var(--learn-control-line)'
- }`,
+ border:
+ pickedIdx === idx
+ ? '2px solid #2080f0'
+ : `1.5px ${submitted ? 'solid' : 'dashed'} ${
+ lineStatus(idx) === 'correct'
+ ? '#18a058'
+ : lineStatus(idx) === 'wrong'
+ ? '#d03050'
+ : 'var(--learn-control-line)'
+ }`,
background:
- lineStatus(idx) === 'correct'
- ? 'rgba(24,160,88,0.08)'
- : lineStatus(idx) === 'wrong'
- ? 'rgba(208,48,80,0.07)'
- : 'transparent',
+ pickedIdx === idx
+ ? 'rgba(32,128,240,0.08)'
+ : lineStatus(idx) === 'correct'
+ ? 'rgba(24,160,88,0.08)'
+ : lineStatus(idx) === 'wrong'
+ ? 'rgba(208,48,80,0.07)'
+ : 'transparent',
cursor: 'grab',
fontFamily: 'var(--learn-code-font)',
userSelect: 'none',
@@ -106,9 +130,13 @@ const lineHtml = computed(() => highlightLines(data.value.lines, props
@dragstart="onDragStart(idx)"
@dragover.prevent
@drop="onDrop(idx)"
+ @click="onLineClick(idx)"
>
⠿
-
+