fix(自学): 手机上「代码排序」「分组」两种练一练拖不动、做不了,改成点一下选中、再点一下放下也行;填空不再被手机输入法把首字母改成大写
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
93f80740ef
commit
96a1c3808f
5 files changed
+82
-11
No files matched your search
@@ -101,7 +101,7 @@ function reset() {
|
|||||||
<span style="color: #bbb; width: 22px; text-align: right; flex-shrink: 0">
|
<span style="color: #bbb; width: 22px; text-align: right; flex-shrink: 0">
|
||||||
{{ idx + 1 }}
|
{{ idx + 1 }}
|
||||||
</span>
|
</span>
|
||||||
<span v-html="lineHtml[idx]" style="white-space: pre" />
|
<span v-html="lineHtml[idx]" style="white-space: pre; min-width: 0; overflow-x: auto" />
|
||||||
</div>
|
</div>
|
||||||
</n-space>
|
</n-space>
|
||||||
|
|
||||||
|
|||||||
@@ -96,6 +96,10 @@ function inputWidth(idx: number): string {
|
|||||||
v-else
|
v-else
|
||||||
:value="userInputs[seg.index]"
|
:value="userInputs[seg.index]"
|
||||||
:disabled="allCorrect"
|
:disabled="allCorrect"
|
||||||
|
autocapitalize="off"
|
||||||
|
autocorrect="off"
|
||||||
|
autocomplete="off"
|
||||||
|
spellcheck="false"
|
||||||
:style="{
|
:style="{
|
||||||
width: inputWidth(seg.index),
|
width: inputWidth(seg.index),
|
||||||
fontFamily: 'var(--learn-code-font)',
|
fontFamily: 'var(--learn-code-font)',
|
||||||
|
|||||||
@@ -13,12 +13,15 @@ const data = computed(() => props.exercise.data as ExerciseGroupData)
|
|||||||
const order = ref<number[]>([]) // item 的稳定展示顺序(初始乱序)
|
const order = ref<number[]>([]) // item 的稳定展示顺序(初始乱序)
|
||||||
const placement = ref<number[]>([]) // placement[itemIdx] = 桶下标,-1 表示在池中
|
const placement = ref<number[]>([]) // placement[itemIdx] = 桶下标,-1 表示在池中
|
||||||
const dragIdx = ref<number | null>(null)
|
const dragIdx = ref<number | null>(null)
|
||||||
|
// 手机上没有 HTML5 拖放(触屏不触发 dragstart),所以再给一条点选的路:先点项目、再点分组
|
||||||
|
const pickedIdx = ref<number | null>(null)
|
||||||
const submitted = ref(false)
|
const submitted = ref(false)
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
order.value = shuffle(data.value.items.map((_, i) => i))
|
order.value = shuffle(data.value.items.map((_, i) => i))
|
||||||
placement.value = Array(data.value.items.length).fill(-1)
|
placement.value = Array(data.value.items.length).fill(-1)
|
||||||
dragIdx.value = null
|
dragIdx.value = null
|
||||||
|
pickedIdx.value = null
|
||||||
submitted.value = false
|
submitted.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,6 +35,7 @@ const locked = computed(() => submitted.value && allCorrect.value)
|
|||||||
function onDragStart(i: number) {
|
function onDragStart(i: number) {
|
||||||
if (locked.value) return
|
if (locked.value) return
|
||||||
dragIdx.value = i
|
dragIdx.value = i
|
||||||
|
pickedIdx.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
function dropTo(bucket: number) {
|
function dropTo(bucket: number) {
|
||||||
@@ -41,6 +45,25 @@ function dropTo(bucket: number) {
|
|||||||
submitted.value = false
|
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))
|
const poolItems = computed(() => order.value.filter((i) => placement.value[i] === -1))
|
||||||
function itemsIn(bucket: number): number[] {
|
function itemsIn(bucket: number): number[] {
|
||||||
return order.value.filter((i) => placement.value[i] === bucket)
|
return order.value.filter((i) => placement.value[i] === bucket)
|
||||||
@@ -56,6 +79,17 @@ function chipStyle(i: number): Record<string, string> {
|
|||||||
const color =
|
const color =
|
||||||
status === "correct" ? "#18a058" : status === "wrong" ? "#d03050" : "var(--learn-control-line)"
|
status === "correct" ? "#18a058" : status === "wrong" ? "#d03050" : "var(--learn-control-line)"
|
||||||
const plain = color === "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 {
|
return {
|
||||||
padding: "6px 12px",
|
padding: "6px 12px",
|
||||||
borderRadius: "6px",
|
borderRadius: "6px",
|
||||||
@@ -86,7 +120,7 @@ function reset() {
|
|||||||
{{ data.question }}
|
{{ data.question }}
|
||||||
</p>
|
</p>
|
||||||
<p style="color: var(--learn-muted); font-size: 13px; margin: 0 0 12px">
|
<p style="color: var(--learn-muted); font-size: 13px; margin: 0 0 12px">
|
||||||
把下面的项目拖到对应的分组里,可在分组间拖动调整。
|
把下面的项目拖到对应的分组里,也可以先点项目、再点分组。
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -102,6 +136,7 @@ function reset() {
|
|||||||
}"
|
}"
|
||||||
@dragover.prevent
|
@dragover.prevent
|
||||||
@drop="dropTo(-1)"
|
@drop="dropTo(-1)"
|
||||||
|
@click="onAreaClick(-1)"
|
||||||
>
|
>
|
||||||
<span v-if="poolItems.length === 0" style="color: var(--learn-muted); font-size: 13px">
|
<span v-if="poolItems.length === 0" style="color: var(--learn-muted); font-size: 13px">
|
||||||
(已全部归类)
|
(已全部归类)
|
||||||
@@ -112,6 +147,7 @@ function reset() {
|
|||||||
draggable="true"
|
draggable="true"
|
||||||
:style="chipStyle(i)"
|
:style="chipStyle(i)"
|
||||||
@dragstart="onDragStart(i)"
|
@dragstart="onDragStart(i)"
|
||||||
|
@click.stop="onItemClick(i)"
|
||||||
>
|
>
|
||||||
{{ data.items[i] }}
|
{{ data.items[i] }}
|
||||||
</div>
|
</div>
|
||||||
@@ -135,6 +171,7 @@ function reset() {
|
|||||||
}"
|
}"
|
||||||
@dragover.prevent
|
@dragover.prevent
|
||||||
@drop="dropTo(b)"
|
@drop="dropTo(b)"
|
||||||
|
@click="onAreaClick(b)"
|
||||||
>
|
>
|
||||||
<p style="font-weight: 600; margin: 0 0 8px; text-align: center; font-size: 14px">
|
<p style="font-weight: 600; margin: 0 0 8px; text-align: center; font-size: 14px">
|
||||||
{{ bucket }}
|
{{ bucket }}
|
||||||
@@ -146,6 +183,7 @@ function reset() {
|
|||||||
draggable="true"
|
draggable="true"
|
||||||
:style="chipStyle(i)"
|
:style="chipStyle(i)"
|
||||||
@dragstart="onDragStart(i)"
|
@dragstart="onDragStart(i)"
|
||||||
|
@click.stop="onItemClick(i)"
|
||||||
>
|
>
|
||||||
{{ data.items[i] }}
|
{{ data.items[i] }}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ function reset() {
|
|||||||
:rows="3"
|
:rows="3"
|
||||||
:disabled="submitted && allCorrect"
|
:disabled="submitted && allCorrect"
|
||||||
placeholder="在这里输入程序会打印的内容"
|
placeholder="在这里输入程序会打印的内容"
|
||||||
|
:input-props="{ autocapitalize: 'off', autocorrect: 'off', spellcheck: false }"
|
||||||
style="font-family: var(--learn-code-font)"
|
style="font-family: var(--learn-code-font)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ function init() {
|
|||||||
;[shuffled[0], shuffled[1]] = [shuffled[1], shuffled[0]]
|
;[shuffled[0], shuffled[1]] = [shuffled[1], shuffled[0]]
|
||||||
}
|
}
|
||||||
lines.value = shuffled
|
lines.value = shuffled
|
||||||
|
pickedIdx.value = null
|
||||||
submitted.value = false
|
submitted.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,19 +32,37 @@ onMounted(init)
|
|||||||
watch(() => props.exercise.id, init)
|
watch(() => props.exercise.id, init)
|
||||||
|
|
||||||
const dragIdx = ref<number | null>(null)
|
const dragIdx = ref<number | null>(null)
|
||||||
|
// 手机上没有 HTML5 拖放(触屏不触发 dragstart),所以再给一条点选的路:先点一行、再点它该去的位置
|
||||||
|
const pickedIdx = ref<number | null>(null)
|
||||||
|
|
||||||
function onDragStart(idx: number) {
|
function onDragStart(idx: number) {
|
||||||
dragIdx.value = idx
|
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) {
|
function onDrop(targetIdx: number) {
|
||||||
if (dragIdx.value === null || dragIdx.value === targetIdx) return
|
if (dragIdx.value === null) return
|
||||||
const newLines = [...lines.value]
|
moveLine(dragIdx.value, targetIdx)
|
||||||
const [moved] = newLines.splice(dragIdx.value, 1)
|
|
||||||
newLines.splice(targetIdx, 0, moved)
|
|
||||||
lines.value = newLines
|
|
||||||
dragIdx.value = null
|
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" {
|
function lineStatus(idx: number): "correct" | "wrong" | "default" {
|
||||||
@@ -86,7 +105,10 @@ const lineHtml = computed<string[]>(() => highlightLines(data.value.lines, props
|
|||||||
gap: '10px',
|
gap: '10px',
|
||||||
padding: '8px 12px',
|
padding: '8px 12px',
|
||||||
borderRadius: '6px',
|
borderRadius: '6px',
|
||||||
border: `1.5px ${submitted ? 'solid' : 'dashed'} ${
|
border:
|
||||||
|
pickedIdx === idx
|
||||||
|
? '2px solid #2080f0'
|
||||||
|
: `1.5px ${submitted ? 'solid' : 'dashed'} ${
|
||||||
lineStatus(idx) === 'correct'
|
lineStatus(idx) === 'correct'
|
||||||
? '#18a058'
|
? '#18a058'
|
||||||
: lineStatus(idx) === 'wrong'
|
: lineStatus(idx) === 'wrong'
|
||||||
@@ -94,7 +116,9 @@ const lineHtml = computed<string[]>(() => highlightLines(data.value.lines, props
|
|||||||
: 'var(--learn-control-line)'
|
: 'var(--learn-control-line)'
|
||||||
}`,
|
}`,
|
||||||
background:
|
background:
|
||||||
lineStatus(idx) === 'correct'
|
pickedIdx === idx
|
||||||
|
? 'rgba(32,128,240,0.08)'
|
||||||
|
: lineStatus(idx) === 'correct'
|
||||||
? 'rgba(24,160,88,0.08)'
|
? 'rgba(24,160,88,0.08)'
|
||||||
: lineStatus(idx) === 'wrong'
|
: lineStatus(idx) === 'wrong'
|
||||||
? 'rgba(208,48,80,0.07)'
|
? 'rgba(208,48,80,0.07)'
|
||||||
@@ -106,9 +130,13 @@ const lineHtml = computed<string[]>(() => highlightLines(data.value.lines, props
|
|||||||
@dragstart="onDragStart(idx)"
|
@dragstart="onDragStart(idx)"
|
||||||
@dragover.prevent
|
@dragover.prevent
|
||||||
@drop="onDrop(idx)"
|
@drop="onDrop(idx)"
|
||||||
|
@click="onLineClick(idx)"
|
||||||
>
|
>
|
||||||
<span style="color: #bbb; cursor: grab">⠿</span>
|
<span style="color: #bbb; cursor: grab">⠿</span>
|
||||||
<span v-html="lineHtml[line.originalIdx]" style="white-space: pre" />
|
<span
|
||||||
|
v-html="lineHtml[line.originalIdx]"
|
||||||
|
style="white-space: pre; min-width: 0; overflow-x: auto"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</n-space>
|
</n-space>
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user