fix
This commit is contained in:
@@ -4,12 +4,16 @@
|
||||
v-for="(question, questionIndex) in questions"
|
||||
:key="question.id"
|
||||
class="exam-question-builder__card"
|
||||
:class="{ 'exam-question-builder__card--readonly': isLocked(question) }"
|
||||
>
|
||||
<div class="exam-question-builder__head">
|
||||
<p class="exam-question-builder__title">سوال شماره {{ questionIndex + 1 }}</p>
|
||||
<p class="exam-question-builder__title">
|
||||
سوال شماره {{ questionIndex + 1 }}
|
||||
<span v-if="isLocked(question)" class="exam-question-builder__lock">(ذخیره شده)</span>
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
:disabled="disabled"
|
||||
:disabled="disabled || isLocked(question)"
|
||||
class="exam-question-builder__remove"
|
||||
aria-label="حذف سوال"
|
||||
@click="removeQuestion(question.id)"
|
||||
@@ -22,24 +26,24 @@
|
||||
<div class="exam-question-builder__col exam-question-builder__col--main">
|
||||
<label class="exam-question-builder__label">متن سوال</label>
|
||||
<textarea
|
||||
:value="question.title"
|
||||
:disabled="disabled"
|
||||
:value="question.questionText"
|
||||
:disabled="disabled || isLocked(question)"
|
||||
rows="1"
|
||||
placeholder="لطفا سوال خود را وارد کنید"
|
||||
class="exam-question-builder__textarea"
|
||||
@input="updateQuestion(question.id, { title: $event.target.value })"
|
||||
@input="updateQuestion(question.id, { questionText: $event.target.value })"
|
||||
/>
|
||||
</div>
|
||||
<div class="exam-question-builder__col exam-question-builder__col--score">
|
||||
<label class="exam-question-builder__label">بارم نمره</label>
|
||||
<label class="exam-question-builder__label">ترتیب</label>
|
||||
<input
|
||||
:value="question.score"
|
||||
:disabled="disabled"
|
||||
:value="question.position"
|
||||
:disabled="disabled || isLocked(question)"
|
||||
type="text"
|
||||
inputmode="numeric"
|
||||
placeholder="15"
|
||||
placeholder="1"
|
||||
class="exam-question-builder__input"
|
||||
@input="updateQuestion(question.id, { score: $event.target.value })"
|
||||
@input="updateQuestion(question.id, { position: $event.target.value })"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -49,31 +53,31 @@
|
||||
<p class="exam-question-builder__label">گزینهها</p>
|
||||
<div class="exam-question-builder__answers-list">
|
||||
<div
|
||||
v-for="answer in question.answers"
|
||||
:key="answer.id"
|
||||
v-for="option in question.options"
|
||||
:key="option.id"
|
||||
class="exam-question-builder__answer"
|
||||
>
|
||||
<input
|
||||
:value="answer.title"
|
||||
:disabled="disabled"
|
||||
:value="option.optionText"
|
||||
:disabled="disabled || isLocked(question)"
|
||||
type="text"
|
||||
placeholder="متن گزینه"
|
||||
class="exam-question-builder__answer-input"
|
||||
@input="updateOption(question.id, answer.id, $event.target.value)"
|
||||
@input="updateOption(question.id, option.id, $event.target.value)"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
:disabled="disabled"
|
||||
:disabled="disabled || isLocked(question)"
|
||||
class="exam-question-builder__answer-remove"
|
||||
aria-label="حذف گزینه"
|
||||
@click="removeOption(question.id, answer.id)"
|
||||
@click="removeOption(question.id, option.id)"
|
||||
>
|
||||
<SvgIcon name="close" :size="14" color="#b1b1b1" />
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
:disabled="disabled"
|
||||
:disabled="disabled || isLocked(question)"
|
||||
class="exam-question-builder__answer-add"
|
||||
aria-label="افزودن گزینه"
|
||||
@click="addOption(question.id)"
|
||||
@@ -85,16 +89,14 @@
|
||||
|
||||
<div class="exam-question-builder__correct">
|
||||
<SelectField
|
||||
:model-value="question.correctAnswerId"
|
||||
:model-value="correctOptionId(question)"
|
||||
:name="`correctAnswer-${question.id}`"
|
||||
:options="correctAnswerOptions(question)"
|
||||
:options="correctOptions(question)"
|
||||
option-label="label"
|
||||
option-value="value"
|
||||
label="گزینه صحیح"
|
||||
:disabled="disabled || question.answers.length === 0"
|
||||
@update:model-value="
|
||||
(value) => updateQuestion(question.id, { correctAnswerId: value || null })
|
||||
"
|
||||
:disabled="disabled || isLocked(question) || question.options.length === 0"
|
||||
@update:model-value="(value) => setCorrectOption(question.id, value)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -128,22 +130,26 @@ const defaultLabels = ['گزینه اول', 'گزینه دوم', 'گزینه س
|
||||
|
||||
const questions = computed(() => (Array.isArray(props.modelValue) ? props.modelValue : []))
|
||||
|
||||
// Backend has no PATCH/DELETE for questions/options — once a question came
|
||||
// from the server (numeric id, no `__local`), the form locks editing it.
|
||||
const isLocked = (question) => question?.__local !== true
|
||||
|
||||
const cloneQuestions = () =>
|
||||
questions.value.map((q) => ({
|
||||
...q,
|
||||
answers: Array.isArray(q.answers) ? q.answers.map((a) => ({ ...a })) : [],
|
||||
options: Array.isArray(q.options) ? q.options.map((o) => ({ ...o })) : [],
|
||||
}))
|
||||
|
||||
const createId = (prefix) => `${prefix}-${Date.now()}-${Math.random().toString(16).slice(2)}`
|
||||
|
||||
const createOption = () => ({ id: createId('answer'), title: '' })
|
||||
const createOption = () => ({ id: createId('option'), optionText: '', isCorrect: false })
|
||||
|
||||
const createQuestion = () => ({
|
||||
id: createId('question'),
|
||||
title: '',
|
||||
score: '',
|
||||
correctAnswerId: null,
|
||||
answers: [createOption(), createOption()],
|
||||
questionText: '',
|
||||
position: questions.value.length + 1,
|
||||
options: [createOption(), createOption()],
|
||||
__local: true,
|
||||
})
|
||||
|
||||
const emitQuestions = (next) => emit('update:modelValue', next)
|
||||
@@ -163,39 +169,48 @@ const updateQuestion = (questionId, patch) => {
|
||||
const addOption = (questionId) => {
|
||||
const next = cloneQuestions().map((q) => {
|
||||
if (q.id !== questionId) return q
|
||||
return { ...q, answers: [...q.answers, createOption()] }
|
||||
return { ...q, options: [...q.options, createOption()] }
|
||||
})
|
||||
emitQuestions(next)
|
||||
}
|
||||
|
||||
const removeOption = (questionId, answerId) => {
|
||||
const removeOption = (questionId, optionId) => {
|
||||
const next = cloneQuestions().map((q) => {
|
||||
if (q.id !== questionId) return q
|
||||
const options = q.options.filter((o) => o.id !== optionId)
|
||||
return { ...q, options }
|
||||
})
|
||||
emitQuestions(next)
|
||||
}
|
||||
|
||||
const updateOption = (questionId, optionId, optionText) => {
|
||||
const next = cloneQuestions().map((q) => {
|
||||
if (q.id !== questionId) return q
|
||||
const answers = q.answers.filter((a) => a.id !== answerId)
|
||||
return {
|
||||
...q,
|
||||
answers,
|
||||
correctAnswerId: String(q.correctAnswerId) === String(answerId) ? null : q.correctAnswerId,
|
||||
options: q.options.map((o) => (o.id === optionId ? { ...o, optionText } : o)),
|
||||
}
|
||||
})
|
||||
emitQuestions(next)
|
||||
}
|
||||
|
||||
const updateOption = (questionId, answerId, title) => {
|
||||
const setCorrectOption = (questionId, optionId) => {
|
||||
const next = cloneQuestions().map((q) => {
|
||||
if (q.id !== questionId) return q
|
||||
return {
|
||||
...q,
|
||||
answers: q.answers.map((a) => (a.id === answerId ? { ...a, title } : a)),
|
||||
options: q.options.map((o) => ({ ...o, isCorrect: String(o.id) === String(optionId) })),
|
||||
}
|
||||
})
|
||||
emitQuestions(next)
|
||||
}
|
||||
|
||||
const correctOptionId = (question) => question.options.find((o) => o.isCorrect)?.id ?? null
|
||||
|
||||
const optionLabel = (index) => defaultLabels[index] || `گزینه ${index + 1}`
|
||||
|
||||
const correctAnswerOptions = (question) =>
|
||||
question.answers.map((a, idx) => ({ value: a.id, label: optionLabel(idx) }))
|
||||
const correctOptions = (question) =>
|
||||
question.options.map((o, idx) => ({ value: o.id, label: optionLabel(idx) }))
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -209,6 +224,10 @@ const correctAnswerOptions = (question) =>
|
||||
border-radius: 1.5rem;
|
||||
padding: 0.875rem;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 2.5%);
|
||||
|
||||
&--readonly {
|
||||
background: rgba(0, 0, 0, 2%);
|
||||
}
|
||||
}
|
||||
|
||||
&__head {
|
||||
@@ -227,6 +246,12 @@ const correctAnswerOptions = (question) =>
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&__lock {
|
||||
font-size: 0.7rem;
|
||||
color: #9c9c9c;
|
||||
margin-inline-start: 0.25rem;
|
||||
}
|
||||
|
||||
&__remove {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
|
||||
Reference in New Issue
Block a user