feat: scope teacher training tasks to classes

dev-QQq
chenyuan 2 months ago
parent d7c8eb8488
commit c0191fe84e

@ -1,6 +1,23 @@
<template>
<div class="training-task-page app-container">
<section class="task-filter">
<div class="class-filter">
<span class="filter-label">教学班</span>
<el-select
v-model="selectedTeachingClassId"
:loading="classesLoading"
placeholder="请选择教学班"
class="class-select"
@change="queryTasks"
>
<el-option
v-for="schoolClass in teachingClassList"
:key="schoolClass.schoolClassId"
:label="schoolClass.className"
:value="schoolClass.schoolClassId"
/>
</el-select>
</div>
<div class="project-filter">
<span class="filter-label">所属项目</span>
<button
@ -17,10 +34,7 @@
<el-input v-model.trim="keyword" clearable placeholder="请输入实训任务名称" @keyup.enter="queryTasks" />
<el-button type="primary" @click="queryTasks"></el-button>
<div class="toolbar-actions">
<button type="button" class="tool-link" @click="downloadTemplate"></button>
<el-upload action="#" :auto-upload="false" :show-file-list="false" :on-change="handleImportChange">
<button type="button" class="tool-link">导入配置</button>
</el-upload>
<el-tag v-if="selectedTeachingClass" type="success"></el-tag>
</div>
</div>
</section>
@ -94,10 +108,6 @@
<el-input v-model="taskForm.title" placeholder="保存后会同步为学生端左侧导航名称" />
</el-form-item>
<el-form-item label="任务分析:">
<el-input v-model="taskForm.knowledge" type="textarea" :rows="4" placeholder="请输入任务分析" />
</el-form-item>
<el-form-item label="实训背景:">
<el-input v-model="taskForm.background" type="textarea" :rows="5" placeholder="请输入实训背景" />
</el-form-item>
@ -172,29 +182,40 @@
<script setup>
import {
createTrainingTask,
downloadTrainingTaskTemplate,
importTrainingTasks,
listTrainingTasks,
updateTrainingTask,
listClassTrainingTasks,
saveClassTrainingTask,
uploadTrainingTaskMaterial,
} from "@/api/trainingTask";
import * as indexApi from "@/api/teacher";
import useUserStore from "@/store/modules/user";
const { proxy } = getCurrentInstance();
const userStore = useUserStore();
const activeProject = ref("");
const keyword = ref("");
const loading = ref(false);
const classesLoading = ref(false);
const saving = ref(false);
const materialUploading = ref(false);
const tasks = ref([]);
const classList = ref([]);
const selectedTeachingClassId = ref("");
const dialogVisible = ref(false);
const dialogMode = ref("view");
const currentTaskId = ref("");
const taskForm = reactive(createEmptyTask());
const currentUserId = computed(() => userStore.userInfo.userId);
const isReadonly = computed(() => dialogMode.value === "view");
const hasTaskMaterial = computed(() => isRealMaterial(taskForm.materialName, taskForm.materialUrl));
const projects = computed(() => [...new Set(tasks.value.map((task) => task.project).filter(Boolean))]);
const teachingClassList = computed(() => {
return classList.value.filter((schoolClass) => {
return normalizeClassType(schoolClass) === "TEACHING" && String(schoolClass.createdBy || "") === String(currentUserId.value || "");
});
});
const selectedTeachingClass = computed(() => {
return teachingClassList.value.find((schoolClass) => schoolClass.schoolClassId === selectedTeachingClassId.value);
});
const filteredTasks = computed(() => {
const text = keyword.value.trim();
@ -206,7 +227,7 @@ const filteredTasks = computed(() => {
});
onMounted(() => {
queryTasks();
loadTeachingClasses();
});
function toggleProject(project) {
@ -214,9 +235,14 @@ function toggleProject(project) {
}
async function queryTasks() {
if (!selectedTeachingClassId.value) {
tasks.value = [];
activeProject.value = "";
return;
}
loading.value = true;
try {
const res = await listTrainingTasks({ enabledOnly: false });
const res = await listClassTrainingTasks(selectedTeachingClassId.value, { enabledOnly: false });
tasks.value = (res.data || []).map(normalizeTask);
if (activeProject.value && !projects.value.includes(activeProject.value)) {
activeProject.value = "";
@ -228,6 +254,27 @@ async function queryTasks() {
}
}
async function loadTeachingClasses() {
classesLoading.value = true;
try {
const res = await indexApi.getClassListBySchoolId({ schoolId: userStore.userInfo.schoolId });
classList.value = res.data || [];
selectedTeachingClassId.value = teachingClassList.value[0]?.schoolClassId || "";
await queryTasks();
} catch (error) {
classList.value = [];
selectedTeachingClassId.value = "";
tasks.value = [];
} finally {
classesLoading.value = false;
}
}
function normalizeClassType(schoolClass) {
if (schoolClass?.classType) return schoolClass.classType;
return schoolClass?.createdBy ? "TEACHING" : "ADMIN";
}
function viewTask(task) {
openTaskDialog(task, "view");
}
@ -242,7 +289,6 @@ function createEmptyTask() {
taskKey: "",
project: "",
title: "",
knowledge: "",
background: "",
objectives: [""],
requirements: "",
@ -261,7 +307,6 @@ function normalizeTask(task) {
taskKey: task.taskKey,
project: task.projectName,
title: task.taskName,
knowledge: task.knowledge || "",
background: task.background || "",
objectives: parseArray(task.objectives, [""]),
requirements: task.requirements || "",
@ -316,7 +361,6 @@ function normalizeSteps(steps) {
function openTaskDialog(task, mode) {
dialogMode.value = mode;
currentTaskId.value = task.id;
Object.assign(taskForm, cloneTask(task));
dialogVisible.value = true;
}
@ -338,13 +382,6 @@ function removeObjective(index) {
taskForm.objectives.splice(index, 1);
}
async function handleImportChange(file) {
if (!file?.raw) return;
await importTrainingTasks(file.raw);
proxy?.$modal?.msgSuccess("导入成功");
queryTasks();
}
async function handleMaterialChange(file) {
const rawFile = file?.raw;
if (!rawFile) return;
@ -363,26 +400,14 @@ async function handleMaterialChange(file) {
}
}
async function downloadTemplate() {
const blob = await downloadTrainingTaskTemplate();
const url = URL.createObjectURL(new Blob([blob]));
const link = document.createElement("a");
link.href = url;
link.download = "实训任务导入模板.xlsx";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
}
async function submitTask() {
if (!selectedTeachingClass.value || !taskForm.taskKey) {
proxy?.$modal?.msgError?.("请选择教学班后再保存");
return;
}
saving.value = true;
try {
if (currentTaskId.value) {
await updateTrainingTask(currentTaskId.value, buildPayload());
} else {
await createTrainingTask(buildPayload());
}
await saveClassTrainingTask(selectedTeachingClassId.value, taskForm.taskKey, buildPayload());
dialogVisible.value = false;
proxy?.$modal?.msgSuccess("保存成功");
await queryTasks();
@ -396,7 +421,6 @@ function buildPayload() {
projectName: taskForm.project,
taskKey: taskForm.taskKey,
taskName: taskForm.title,
knowledge: taskForm.knowledge,
background: taskForm.background,
objectives: JSON.stringify(taskForm.objectives.map((item) => item.trim()).filter(Boolean)),
requirements: taskForm.requirements,
@ -421,6 +445,7 @@ function buildPayload() {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 24px;
padding: 20px 22px;
margin: 0 0 32px;
@ -455,6 +480,21 @@ function buildPayload() {
}
}
.class-filter {
position: relative;
z-index: 1;
display: flex;
align-items: center;
flex: 0 0 300px;
gap: 10px;
min-width: 280px;
}
.class-select {
flex: 1;
min-width: 180px;
}
.project-filter {
display: flex;
align-items: center;

Loading…
Cancel
Save