Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c47e9389b | ||
|
|
96a1c3808f |
No files matched your search
@@ -101,7 +101,7 @@ function reset() {
|
||||
<span style="color: #bbb; width: 22px; text-align: right; flex-shrink: 0">
|
||||
{{ idx + 1 }}
|
||||
</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>
|
||||
</n-space>
|
||||
|
||||
|
||||
@@ -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)',
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import ExerciseResult from "./ExerciseResult.vue"
|
||||
import type { Exercise, ExerciseGroupData } from "utils/types"
|
||||
import { shuffle } from "../composables/useShuffle"
|
||||
import { useBreakpoints } from "shared/composables/breakpoints"
|
||||
|
||||
const props = defineProps<{ exercise: Exercise; lang?: string }>()
|
||||
const emit = defineEmits<{
|
||||
@@ -9,16 +10,20 @@ const emit = defineEmits<{
|
||||
}>()
|
||||
|
||||
const data = computed(() => props.exercise.data as ExerciseGroupData)
|
||||
const { isDesktop } = useBreakpoints()
|
||||
|
||||
const order = ref<number[]>([]) // item 的稳定展示顺序(初始乱序)
|
||||
const placement = ref<number[]>([]) // placement[itemIdx] = 桶下标,-1 表示在池中
|
||||
const dragIdx = ref<number | null>(null)
|
||||
// 手机上没有 HTML5 拖放(触屏不触发 dragstart),所以再给一条点选的路:先点项目、再点分组
|
||||
const pickedIdx = ref<number | null>(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 +37,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 +47,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 +81,17 @@ function chipStyle(i: number): Record<string, string> {
|
||||
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 +122,11 @@ function reset() {
|
||||
{{ data.question }}
|
||||
</p>
|
||||
<p style="color: var(--learn-muted); font-size: 13px; margin: 0 0 12px">
|
||||
把下面的项目拖到对应的分组里,可在分组间拖动调整。
|
||||
{{
|
||||
isDesktop
|
||||
? "把下面的项目拖到对应的分组里,可在分组间拖动调整。"
|
||||
: "先点一个项目,再点它该去的分组。"
|
||||
}}
|
||||
</p>
|
||||
|
||||
<div
|
||||
@@ -102,6 +142,7 @@ function reset() {
|
||||
}"
|
||||
@dragover.prevent
|
||||
@drop="dropTo(-1)"
|
||||
@click="onAreaClick(-1)"
|
||||
>
|
||||
<span v-if="poolItems.length === 0" style="color: var(--learn-muted); font-size: 13px">
|
||||
(已全部归类)
|
||||
@@ -112,6 +153,7 @@ function reset() {
|
||||
draggable="true"
|
||||
:style="chipStyle(i)"
|
||||
@dragstart="onDragStart(i)"
|
||||
@click.stop="onItemClick(i)"
|
||||
>
|
||||
{{ data.items[i] }}
|
||||
</div>
|
||||
@@ -135,6 +177,7 @@ function reset() {
|
||||
}"
|
||||
@dragover.prevent
|
||||
@drop="dropTo(b)"
|
||||
@click="onAreaClick(b)"
|
||||
>
|
||||
<p style="font-weight: 600; margin: 0 0 8px; text-align: center; font-size: 14px">
|
||||
{{ bucket }}
|
||||
@@ -146,6 +189,7 @@ function reset() {
|
||||
draggable="true"
|
||||
:style="chipStyle(i)"
|
||||
@dragstart="onDragStart(i)"
|
||||
@click.stop="onItemClick(i)"
|
||||
>
|
||||
{{ data.items[i] }}
|
||||
</div>
|
||||
|
||||
@@ -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)"
|
||||
/>
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import ExerciseResult from "./ExerciseResult.vue"
|
||||
import type { Exercise, ExerciseSortData } from "utils/types"
|
||||
import { shuffle } from "../composables/useShuffle"
|
||||
import { highlightLines } from "shared/highlight"
|
||||
import { useBreakpoints } from "shared/composables/breakpoints"
|
||||
|
||||
const props = defineProps<{ exercise: Exercise; lang?: string }>()
|
||||
const emit = defineEmits<{
|
||||
@@ -10,6 +11,7 @@ const emit = defineEmits<{
|
||||
}>()
|
||||
|
||||
const data = computed(() => props.exercise.data as ExerciseSortData)
|
||||
const { isDesktop } = useBreakpoints()
|
||||
|
||||
type LineItem = { originalIdx: number; text: string }
|
||||
|
||||
@@ -24,6 +26,7 @@ function init() {
|
||||
;[shuffled[0], shuffled[1]] = [shuffled[1], shuffled[0]]
|
||||
}
|
||||
lines.value = shuffled
|
||||
pickedIdx.value = null
|
||||
submitted.value = false
|
||||
}
|
||||
|
||||
@@ -31,19 +34,37 @@ onMounted(init)
|
||||
watch(() => props.exercise.id, init)
|
||||
|
||||
const dragIdx = ref<number | null>(null)
|
||||
// 手机上没有 HTML5 拖放(触屏不触发 dragstart),所以再给一条点选的路:先点一行、再点它该去的位置
|
||||
const pickedIdx = ref<number | null>(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" {
|
||||
@@ -71,9 +92,12 @@ const lineHtml = computed<string[]>(() => highlightLines(data.value.lines, props
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<p style="font-weight: 500; font-size: 16px; margin-bottom: 12px">
|
||||
<p :style="{ fontWeight: 500, fontSize: '16px', marginBottom: isDesktop ? '12px' : '8px' }">
|
||||
{{ data.question }}
|
||||
</p>
|
||||
<p v-if="!isDesktop" style="color: var(--learn-muted); font-size: 13px; margin: 0 0 12px">
|
||||
先点一行,再点它该去的位置。
|
||||
</p>
|
||||
|
||||
<n-space vertical :size="6">
|
||||
<div
|
||||
@@ -86,19 +110,24 @@ const lineHtml = computed<string[]>(() => 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 +135,13 @@ const lineHtml = computed<string[]>(() => highlightLines(data.value.lines, props
|
||||
@dragstart="onDragStart(idx)"
|
||||
@dragover.prevent
|
||||
@drop="onDrop(idx)"
|
||||
@click="onLineClick(idx)"
|
||||
>
|
||||
<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>
|
||||
</n-space>
|
||||
|
||||
|
||||
Reference in new issue
Block a user