feat(M5): P0 契约层 + 审批/应用设置下线
- types/homeworkV2.ts:V2 全量类型契约(五态/枚举/矩阵/版本/通知)
- teacherHomework/studentHomework/notification 三客户端(28 端点)
- §6 审批功能下线:删 ApprovalList/ApprovalSetting/ApprovalFlowEditor,
types/api/menu/menuRoute/App/mock 同步清理,DROPPED_MOD_IDS={5,6}
- §7 应用设置下线:删 HomeworkSetting/TypeSetBox/UserCheckInput,
SettingApi 与 dict/contacts 死代码清理,DROPPED_MOD_IDS 扩 {21}
- 新增 V2 组件:TeacherHomeworks(已发布+草稿箱)、ReviewModal(批阅弹窗)、
AttachmentUpload(附件上传)——尚未挂路由,页面切换随后续阶段
This commit is contained in:
@@ -4,6 +4,20 @@ import { useRoute } from "vue-router";
|
||||
import { Message } from "view-ui-plus";
|
||||
import { homeworkApi } from "@/api/module/homework";
|
||||
import type { SubmissionsResultVo, SubmissionBrief, HomeworkVo } from "@/types/homework";
|
||||
import config from "@/assets/config/host.config";
|
||||
import Utils from "@/utils";
|
||||
|
||||
const serviceBase = config.isMock ? "" : config.serviceHost;
|
||||
function previewUrl(v: number | string) {
|
||||
const s = String(v);
|
||||
if (s.startsWith("data:") || s.startsWith("http")) return s;
|
||||
return `${serviceBase}/api/file/Preview?id=${s}`;
|
||||
}
|
||||
const imgPreview = reactive({ open: false, url: "" });
|
||||
function zoom(url: string) {
|
||||
imgPreview.url = url;
|
||||
imgPreview.open = true;
|
||||
}
|
||||
|
||||
const route = useRoute();
|
||||
const serid = ref<number>(Number(route.query.serid) || 0);
|
||||
@@ -78,13 +92,25 @@ const stuColumns = [
|
||||
title: "操作",
|
||||
width: 110,
|
||||
align: "center",
|
||||
render: (h: any, p: any) => h("Button", { type: "text", size: "small", onClick: () => openGrade(p.row as SubmissionBrief) }, () => (p.row.hasGrade ? "再批" : "批改")),
|
||||
render: (h: any, p: any) => Utils.operatingHandle(h, p.row.hasGrade ? "再批" : "批改", p.row.hasGrade ? "icon-edit" : "icon-chakan", () => openGrade(p.row as SubmissionBrief)),
|
||||
},
|
||||
];
|
||||
|
||||
/* ============ 内联批改(走 Grade 接口,核心闭环即时批改) ============ */
|
||||
const gradeModal = reactive({ open: false, subId: 0, studentName: "", mode: "grade", action: "grade", score: "" as string | number, level: "A", comment: "" });
|
||||
function openGrade(stu: SubmissionBrief) {
|
||||
const gradeModal = reactive({
|
||||
open: false,
|
||||
subId: 0,
|
||||
studentName: "",
|
||||
mode: "grade",
|
||||
action: "grade",
|
||||
score: "" as string | number,
|
||||
level: "A",
|
||||
comment: "",
|
||||
answerText: "",
|
||||
answerImages: [] as string[],
|
||||
loadingAnswer: false,
|
||||
});
|
||||
async function openGrade(stu: SubmissionBrief) {
|
||||
gradeModal.open = true;
|
||||
gradeModal.subId = stu.subId;
|
||||
gradeModal.studentName = stu.studentName;
|
||||
@@ -93,6 +119,18 @@ function openGrade(stu: SubmissionBrief) {
|
||||
gradeModal.score = stu.score ?? "";
|
||||
gradeModal.level = stu.level || "A";
|
||||
gradeModal.comment = "";
|
||||
gradeModal.answerText = "";
|
||||
gradeModal.answerImages = [];
|
||||
gradeModal.loadingAnswer = true;
|
||||
try {
|
||||
const d = await homeworkApi.submissionDetail(stu.subId).send();
|
||||
gradeModal.answerText = d.text || "";
|
||||
gradeModal.answerImages = (d.images || []).map((v: any) => String(v)).filter(Boolean);
|
||||
} catch (e: any) {
|
||||
Message.error(e.message || "加载作答失败");
|
||||
} finally {
|
||||
gradeModal.loadingAnswer = false;
|
||||
}
|
||||
}
|
||||
async function doGrade(action: "grade" | "redo" | "excellent") {
|
||||
const payload: any = { subId: gradeModal.subId, action };
|
||||
@@ -166,7 +204,20 @@ function onSave() {
|
||||
</template>
|
||||
|
||||
<!-- 批改弹窗 -->
|
||||
<Modal v-model="gradeModal.open" :title="`批改 · ${gradeModal.studentName}`" width="440px">
|
||||
<Modal v-model="gradeModal.open" :title="`批改 · ${gradeModal.studentName}`" width="520px">
|
||||
<div class="ans-box">
|
||||
<div class="ans-h">学生作答</div>
|
||||
<Spin v-if="gradeModal.loadingAnswer" />
|
||||
<template v-else>
|
||||
<div class="ans-text">{{ gradeModal.answerText || "(无文字作答)" }}</div>
|
||||
<div class="ans-imgs" v-if="gradeModal.answerImages.length">
|
||||
<div class="ans-img" v-for="(u, ui) in gradeModal.answerImages" :key="ui" @click="zoom(previewUrl(u))">
|
||||
<img :src="previewUrl(u)" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<Divider style="margin: 8px 0" />
|
||||
<Form :label-width="72">
|
||||
<FormItem label="方式">
|
||||
<RadioGroup v-model="gradeModal.mode">
|
||||
@@ -192,15 +243,67 @@ function onSave() {
|
||||
</Form>
|
||||
<template #footer>
|
||||
<Button @click="gradeModal.open = false">取消</Button>
|
||||
<Button type="warning" style="margin-left: 8px" @click="doGrade('excellent')">标为优秀</Button>
|
||||
<Button type="primary" :loading="loading" @click="onSave">保存</Button>
|
||||
<Button type="warning" icon="md-star" style="margin-left: 8px" @click="doGrade('excellent')">标为优秀</Button>
|
||||
<Button type="primary" icon="md-checkmark" :loading="loading" @click="onSave">保存</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
|
||||
<!-- 作答图片放大预览 -->
|
||||
<Modal v-model="imgPreview.open" footer-hide title="作答图片" width="680px">
|
||||
<div class="zoom-wrap"><img :src="imgPreview.url" /></div>
|
||||
</Modal>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.ans-box {
|
||||
.ans-h {
|
||||
font-weight: 600;
|
||||
color: #17233d;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.ans-text {
|
||||
color: #515a6e;
|
||||
line-height: 1.6;
|
||||
background: #fafcff;
|
||||
border: 1px solid #e8eaec;
|
||||
border-radius: 4px;
|
||||
padding: 8px 10px;
|
||||
max-height: 140px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.ans-imgs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 8px;
|
||||
.ans-img {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border: 1px solid #e8eaec;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
cursor: zoom-in;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
&:hover {
|
||||
border-color: #2d8cf0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.zoom-wrap {
|
||||
text-align: center;
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 62vh;
|
||||
}
|
||||
}
|
||||
.tip {
|
||||
color: #808695;
|
||||
padding: 20px 0;
|
||||
|
||||
Reference in New Issue
Block a user