You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
dianshang-qianduan/src/views/components/StepOneQuestionBankPicker.vue

137 lines
6.3 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<el-dialog :model-value="visible" title="从题库选择单选题" width="min(880px, 94vw)" append-to-body
class="step-one-bank-picker" @update:model-value="close">
<div class="bank-picker-intro">
<div>
<span>QUESTION BANK</span>
<p>勾选后确认,题目会复制为当前任务的第一步选择题;仅列出题库中的单选题,与现有题目重复的不再列出。</p>
</div>
<span class="bank-picker-count">已选 {{ checkedIds.size }} 题</span>
</div>
<div v-loading="loading" class="bank-question-list">
<article v-for="(question, index) in selectable" :key="question.id" role="button" tabindex="0"
:class="['bank-question-card', { 'is-checked': checkedIds.has(question.id) }]"
@click="toggle(question)" @keydown.enter.prevent="toggle(question)">
<span class="bank-check">{{ checkedIds.has(question.id) ? '✓' : String(index + 1).padStart(2, '0') }}</span>
<div class="bank-body">
<h4>{{ question.stem }}</h4>
<div class="bank-options">
<span v-for="option in question.options" :key="option.label"><b>{{ option.label }}.</b>{{
option.content }}</span>
</div>
</div>
</article>
<el-empty v-if="!loading && !selectable.length" description="题库暂无可添加的单选题" />
</div>
<template #footer>
<el-button @click="close(false)">取消</el-button>
<el-button type="primary" :disabled="!checkedIds.size" @click="confirmSelection">
添加 {{ checkedIds.size }} 道题
</el-button>
</template>
</el-dialog>
</template>
<script setup>
import { computed, ref, watch } from "vue";
import { ElMessage } from "element-plus";
import { QUESTION_OPTION_KEYS } from "@/utils/stepOneChoiceQuestions";
const props = defineProps({
visible: { type: Boolean, default: false },
// 返回题库单选题列表的异步函数(教师端/平台端各自注入接口)
loadQuestions: { type: Function, required: true },
// 当前已配置的题目,用于按题干去重
existing: { type: Array, default: () => [] },
});
const emit = defineEmits(["update:visible", "confirm"]);
const loading = ref(false);
const questions = ref([]);
const checkedIds = ref(new Set());
const existingTitles = computed(() => new Set((props.existing || []).map((q) => String(q.title || "").trim())));
// 已存在于任务配置中的题不再展示,避免重复添加
const selectable = computed(() => questions.value.filter((q) => !existingTitles.value.has(String(q.stem || "").trim())));
watch(() => props.visible, (opened) => {
if (opened) load();
}, { immediate: true });
async function load() {
checkedIds.value = new Set();
loading.value = true;
try {
questions.value = (await props.loadQuestions()) || [];
} catch {
questions.value = [];
ElMessage.error("题库加载失败,请稍后重试");
} finally {
loading.value = false;
}
}
function toggle(question) {
const next = new Set(checkedIds.value);
next.has(question.id) ? next.delete(question.id) : next.add(question.id);
checkedIds.value = next;
}
function convert(question, index) {
const options = (question.options || [])
.map((option) => ({ key: option.label, content: String(option.content || "").trim() }))
.filter((option) => option.key && option.content);
const keys = options.map((option) => option.key);
const rawAnswer = Array.isArray(question.answer) ? question.answer[0] : question.answer;
const answer = keys.includes(rawAnswer) ? rawAnswer : keys[0] || QUESTION_OPTION_KEYS[0];
return {
id: `step-one-question-${Date.now()}-${index + 1}`,
title: String(question.stem || "").trim(),
options,
answer,
};
}
function confirmSelection() {
const picked = questions.value.filter((q) => checkedIds.value.has(q.id));
if (!picked.length) return;
emit("confirm", picked.map(convert));
close();
}
function close(value = false) {
emit("update:visible", value);
}
</script>
<style lang="scss">
.step-one-bank-picker {
.el-dialog { overflow: hidden; border: 1px solid #dbeafe; background: #ffffff; box-shadow: 0 24px 80px rgba(17, 24, 39, 0.18); }
.el-dialog__header { margin: 0; padding: 20px 24px; border-bottom: 1px solid #dbeafe; }
.el-dialog__title { color: #1f2937; font-size: 18px; font-weight: 800; }
.el-dialog__body { max-height: min(64vh, 700px); overflow-y: auto; padding: 20px 24px; }
.el-dialog__footer { padding: 16px 24px; border-top: 1px solid #dbeafe; }
}
.bank-picker-intro { display:flex; align-items:center; justify-content:space-between; gap:20px; margin-bottom:18px; padding:15px 17px; border-left:3px solid #2563eb; background:linear-gradient(90deg,rgba(219,234,254,.6),transparent); }
/* 只命中左上角标签,避免染蓝按钮内文字 */
.bank-picker-intro > div > span { color:#2563eb; font-size:12px; font-weight:800; letter-spacing:.12em; }
.bank-picker-intro p { margin:5px 0 0; color:#6b7280; line-height:1.6; }
.bank-picker-count { flex-shrink:0; padding:6px 14px; color:#2563eb; font-size:13px; font-weight:800; border:1px solid #dbeafe; border-radius:16px; background:#eff6ff; }
.bank-question-list { display:grid; gap:12px; min-height:120px; }
.bank-question-card { display:flex; gap:12px; padding:14px 16px; border:1px solid #dbeafe; border-radius:10px; background:#ffffff; cursor:pointer; transition:all .16s ease;
&:hover { border-color:#2563eb; background:#f6fafe; }
&.is-checked { border-color:#2563eb; background:#eaf3fd; }
}
.bank-check { flex:0 0 auto; display:grid; place-items:center; width:28px; height:28px; margin-top:2px; color:#2563eb; font-size:12px; font-weight:900; border:1px solid #dbeafe; border-radius:50%; background:#eff6ff; }
.bank-question-card.is-checked .bank-check { color:#ffffff; background:#2563eb; border-color:#2563eb; }
.bank-body { min-width:0;
h4 { margin:0 0 8px; color:#1f2937; font-size:14px; line-height:1.6; }
}
.bank-options { display:grid; gap:4px;
span { color:#6b7280; font-size:13px; line-height:1.55; }
b { margin-right:6px; color:#2563eb; font-weight:700; }
}
</style>