fix: guard class allocation loading state

dev-QQq
chenyuan 4 weeks ago
parent 91c6e21b76
commit 985dbabdf3

@ -46,6 +46,8 @@ const configured = ref(false);
const loading = ref(false);
const saving = ref(false);
const tableRef = ref(null);
let activeLoadGeneration = 0;
let selectionSyncGeneration = -1;
const currentUserId = computed(() => userStore.userInfo.userId);
const teachingClassList = computed(() => classList.value.filter((item) => item.classType === "TEACHING" || !item.classType && item.createdBy));
@ -58,48 +60,68 @@ async function loadClasses() {
classId.value = teachingClassList.value[0]?.schoolClassId || "";
}
async function loadTasks() {
function isActiveLoad(requestedClassId, requestGeneration) {
return classId.value === requestedClassId && requestGeneration === activeLoadGeneration;
}
function syncTableSelection(taskKeys, requestGeneration) {
selectionSyncGeneration = requestGeneration;
tableRef.value?.clearSelection();
proxy.$nextTick(() => {
if (requestGeneration !== activeLoadGeneration) return;
const selected = new Set(taskKeys);
tasks.value.forEach((task) => tableRef.value?.toggleRowSelection(task, selected.has(task.taskKey)));
proxy.$nextTick(() => {
if (requestGeneration === activeLoadGeneration) selectionSyncGeneration = -1;
});
});
}
async function loadTasks(requestGeneration) {
if (!classId.value) {
tasks.value = [];
return;
return false;
}
const requestedClassId = classId.value;
tasks.value = [];
const res = await listClassTrainingTasks(classId.value, { enabledOnly: false });
if (classId.value !== requestedClassId) return;
if (!isActiveLoad(requestedClassId, requestGeneration)) return false;
tasks.value = res.data || [];
return true;
}
function applySelection() {
tableRef.value?.clearSelection();
proxy.$nextTick(() => {
const selected = new Set(selectedTaskKeys.value);
tasks.value.forEach((task) => tableRef.value?.toggleRowSelection(task, selected.has(task.taskKey)));
});
function applySelection(requestGeneration) {
syncTableSelection(selectedTaskKeys.value, requestGeneration);
}
async function loadPublication() {
const requestGeneration = ++activeLoadGeneration;
selectedTaskKeys.value = [];
configured.value = false;
if (!classId.value) return;
syncTableSelection([], requestGeneration);
if (!classId.value) {
tasks.value = [];
return;
}
const requestedClassId = classId.value;
loading.value = true;
try {
await loadTasks();
if (classId.value !== requestedClassId) return;
await loadTasks(requestGeneration);
if (!isActiveLoad(requestedClassId, requestGeneration)) return;
const res = await indexApi.getTaskPublication({ classId: requestedClassId });
if (classId.value !== requestedClassId) return;
if (!isActiveLoad(requestedClassId, requestGeneration)) return;
configured.value = res.data?.configured === true;
selectedTaskKeys.value = configured.value
? (res.data?.taskKeys || [])
: tasks.value.map((task) => task.taskKey);
applySelection();
applySelection(requestGeneration);
} finally {
loading.value = false;
if (requestGeneration === activeLoadGeneration) loading.value = false;
}
}
function handleSelectionChange(selection) {
if (selectionSyncGeneration === activeLoadGeneration) return;
selectedTaskKeys.value = selection.map((task) => task.taskKey);
}

@ -8,12 +8,17 @@ const taskPage = read("src/views/teacherEnd/task/index.vue");
const sidebar = read("src/layout/components/Sidebar/index.vue");
const api = read("src/api/teacher.js");
assert(taskPage.includes("listClassTrainingTasks"), "task allocation must use training tasks as its data source");
assert(taskPage.includes('import { listClassTrainingTasks } from "@/api/trainingTask";'), "task allocation must import the class-scoped training task source");
assert(taskPage.includes("listClassTrainingTasks"), "task allocation must load training tasks for the selected teaching class");
assert(taskPage.includes("await listClassTrainingTasks(classId.value, { enabledOnly: false })"), "class task loading must request disabled and enabled tasks");
assert(taskPage.includes("tasks.value = [];\n const res = await listClassTrainingTasks"), "class task loading must clear prior class rows before requesting the selected class");
assert((taskPage.match(/const requestedClassId = classId\.value;/g) || []).length >= 2, "task and publication loading must capture the requested class");
assert((taskPage.match(/if \(classId\.value !== requestedClassId\) return;/g) || []).length >= 2, "stale class requests must not update task rows or publication selection");
assert((taskPage.match(/if \(!isActiveLoad\(requestedClassId, requestGeneration\)\) return/g) || []).length >= 2, "stale class requests must not update task rows or publication selection");
assert(taskPage.includes("let activeLoadGeneration = 0;"), "overlapping class loads must use a shared generation");
assert(taskPage.includes("const requestGeneration = ++activeLoadGeneration;"), "each class load must advance the active generation");
assert(taskPage.includes("if (requestGeneration === activeLoadGeneration) loading.value = false;"), "only the active class load may clear the loading indicator");
assert(taskPage.includes("let selectionSyncGeneration = -1;"), "programmatic selection synchronization must be tracked");
assert(taskPage.includes("if (selectionSyncGeneration === activeLoadGeneration) return;"), "programmatic table selection events must not overwrite task keys");
assert(taskPage.includes("getTaskPublication"), "task allocation must load the selected class publication state");
assert(taskPage.includes("tasks.value.map((task) => task.taskKey)"), "an unconfigured class must default to all tasks selected");
assert(taskPage.includes("保存发布配置"), "task allocation must require an explicit save");

Loading…
Cancel
Save