feat: 提交课程作业前端工程源码(Vue3 + Vite + view-ui-plus)

This commit is contained in:
lijing
2026-09-21 12:14:00 +08:00
parent 0749e0b9c5
commit 66cdf7c5a1
358 changed files with 40943 additions and 2 deletions
+239
View File
@@ -0,0 +1,239 @@
<script setup lang="ts">
import { ref, computed, onMounted, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import { Message } from "view-ui-plus";
import { teacherApi } from "@/api/module/teacherHomework";
import type { HomeworkV2Vo, StudentMatrixVo } from "@/types/homeworkV2";
import ReviewWorkbench from "@/components/ReviewWorkbench.vue";
const route = useRoute();
const router = useRouter();
const serid = ref<number>(Number(route.query.serid) || 0);
/* ============ 待批作业(已发布且 pendingGrade>0,按待批数降序) ============ */
const homeworks = ref<HomeworkV2Vo[]>([]);
const loadingHw = ref(false);
async function loadHomeworks() {
loadingHw.value = true;
try {
const r = await teacherApi.published({ pageIndex: 1, pageSize: 100 }).send();
homeworks.value = (r.content || [])
.filter((h) => (h.progress?.pendingGrade ?? 0) > 0)
.sort((a, b) => (b.progress?.pendingGrade ?? 0) - (a.progress?.pendingGrade ?? 0));
// 未带 serid 进入时自动选中第一份待批作业
if (!serid.value && homeworks.value.length) {
pickHw(homeworks.value[0].id);
}
} catch (e: any) {
Message.error(e.message || "加载待批作业失败");
} finally {
loadingHw.value = false;
}
}
/* ============ 待批队列(矩阵 pendingGrade=true) ============ */
const queue = ref<StudentMatrixVo[]>([]);
const idx = ref(0);
const loadingQueue = ref(false);
const homeworkTitle = ref("");
const current = computed(() => queue.value[idx.value] || null);
async function loadQueue() {
if (!serid.value) {
queue.value = [];
homeworkTitle.value = "";
return;
}
loadingQueue.value = true;
try {
const hw = homeworks.value.find((h) => h.id === serid.value);
homeworkTitle.value = hw ? hw.title : "";
const r = await teacherApi.students(serid.value, { pendingGrade: true, pageIndex: 1, pageSize: 200 }).send();
queue.value = r.content || [];
idx.value = 0;
} catch (e: any) {
Message.error(e.message || "加载待批名单失败");
queue.value = [];
} finally {
loadingQueue.value = false;
}
}
/* 批阅/打回完成:移出队列并前移;全批完则刷新作业列表 */
function onSaved() {
const done = idx.value;
queue.value.splice(done, 1);
if (queue.value.length === 0) {
if (homeworks.value.length <= 1) {
homeworks.value = [];
serid.value = 0;
router.replace({ path: "/grade" });
return;
}
loadHomeworks();
return;
}
idx.value = Math.min(done, queue.value.length - 1);
}
function select(i: number) {
if (i < 0 || i >= queue.value.length) return;
idx.value = i;
}
/** 强制工作台重新加载(同一学生打回后重交再进队列) */
const workbenchKey = computed(() => `${serid.value}-${current.value?.studentId ?? ""}-${idx.value}`);
function pickHw(v: number) {
serid.value = v;
router.replace({ path: "/grade", query: v ? { serid: String(v) } : {} });
loadQueue();
}
watch(
() => route.query.serid,
(v) => {
const n = Number(v) || 0;
if (n !== serid.value) {
serid.value = n;
loadQueue();
}
}
);
onMounted(async () => {
await loadHomeworks();
loadQueue();
});
</script>
<template>
<div class="page-wrapper">
<div class="main-container">
<!-- 顶部:待批作业选择 -->
<div class="fix mb-12">
<div class="fl" style="display: flex; align-items: center; gap: 8px">
<span style="color: #515a6e">待批作业:</span>
<Select :model-value="serid" style="width: 380px" placeholder="选择一份有待批的作业" filterable :loading="loadingHw" @on-change="pickHw">
<Option v-for="hw in homeworks" :key="hw.id" :value="hw.id">
{{ hw.subject ? hw.subject + " · " : "" }}{{ hw.title }}(待批 {{ hw.progress!.pendingGrade }})
</Option>
</Select>
</div>
</div>
<p v-if="!homeworks.length" class="tip">🎉 当前没有待批改的作业。</p>
<p v-else-if="!serid" class="tip">请从上方选择一份待批改的作业。</p>
<div v-else class="workbench fix">
<!-- 左:待批队列 -->
<div class="wb-left fl">
<div class="wb-h">待批队列({{ queue.length }} 份)</div>
<p v-if="!loadingQueue && !queue.length" class="done">🎉 该作业已全部批改完成</p>
<Spin v-if="loadingQueue" fix />
<ul class="qlist">
<li v-for="(s, i) in queue" :key="s.studentId" :class="{ act: i === idx }" @click="select(i)">
<div class="q1">
{{ s.studentName }}
<span v-if="s.isLate" class="q-late">逾期</span>
</div>
<div class="q2">{{ s.className }} · {{ s.submittedAt ? String(s.submittedAt).slice(5, 16) : "" }}</div>
</li>
</ul>
</div>
<!-- 右:提交内容 + 批阅(版本切换/批阅/打回共用工作台) -->
<div class="wb-right fr">
<div class="wb-h">
{{ homeworkTitle }}
<span class="pos" v-if="current">第 {{ idx + 1 }} / {{ queue.length }} 份 · {{ current.studentName }}({{ current.className }})</span>
</div>
<div class="wb-panel" v-if="current">
<ReviewWorkbench :key="workbenchKey" :homeworkId="serid" :student="current" @saved="onSaved" />
</div>
<p v-else class="tip">该作业暂无待批改提交。</p>
</div>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.tip {
color: #808695;
padding: 20px 0;
}
.done {
color: #19be6b;
padding: 16px 8px;
}
.workbench {
display: flex;
gap: 16px;
align-items: stretch;
}
.wb-h {
font-weight: 600;
color: #17233d;
padding: 8px 0;
border-bottom: 1px solid #e8eaec;
.pos {
font-weight: 400;
color: #808695;
font-size: 13px;
margin-left: 10px;
}
}
.wb-left {
width: 240px;
flex: none;
border: 1px solid #e8eaec;
border-radius: 4px;
padding: 0 10px 10px;
position: relative;
max-height: calc(100vh - 220px);
overflow-y: auto;
.qlist {
list-style: none;
margin: 0;
padding: 0;
li {
padding: 8px 10px;
border-radius: 4px;
cursor: pointer;
.q1 {
color: #17233d;
display: flex;
align-items: center;
gap: 6px;
}
.q-late {
background: #ff9900;
color: #fff;
border-radius: 3px;
font-size: 11px;
padding: 0 4px;
}
.q2 {
font-size: 12px;
color: #808695;
}
&.act {
background: #e8f3ff;
}
&:hover {
background: #f5f7fa;
}
}
}
}
.wb-right {
flex: 1;
min-width: 0;
border: 1px solid #e8eaec;
border-radius: 4px;
padding: 0 14px 14px;
position: relative;
.wb-panel {
padding-top: 6px;
max-height: calc(100vh - 260px);
overflow-y: auto;
}
}
</style>