feat: 新建课程作业模块(基于校园公告模板),实现 v1 教师端与 v2 学生/家长端
以 CampusAnnouncement 为模板建立课程作业模块,重命名标识符与文案(notice→homework、公告→作业), 并保留其顶栏/左栏外壳,按课程作业业务重搭数据模型与页面。 - mock(src/assets/mock/homeworkapi.ts):作业化数据模型(学科/截止/提交方式/流程状态), 班级花名册→学生个体提交记录与批改(确定性分布),端点 List/SendList/Submissions/ SubmissionDetail/Grade/Remind/Statis 等;左栏菜单按角色返回。 - v1 教师端:布置作业(新增学科/截止/提交方式)、我布置的(状态标签+提交/批改进度)、 提交情况(Submissions,含内联批改)、批改中心(Grade 逐份批改)、作业统计(成绩分布+进度)。 - v2 学生/家长端(端分离):SetRole + viewerRole 切换身份;StudentHomeworks.vue 承担 今日作业/待订正(学生可提交/重交)与孩子作业(家长只读+提醒);新增 MyHomeworks/Submit/ MySubmission/ParentNudge 接口与类型。 - 类型与 api 模块同步;dev:mock 三角色 curl 端到端验证通过。
This commit is contained in:
+133
@@ -0,0 +1,133 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, watch } from "vue";
|
||||
import layout from "./components/layout.vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import config from "@/assets/config/host.config";
|
||||
import { baseApi } from "@/api/module/homework";
|
||||
import { resolveMenuPage } from "@/utils/menuRoute";
|
||||
import { Message } from "view-ui-plus";
|
||||
|
||||
const router = useRouter();
|
||||
const isLogin = ref(false);
|
||||
const layoutData = reactive({
|
||||
appName: "课程作业",
|
||||
baseHost: config.baseHost,
|
||||
ssoHost: config.ssoHost,
|
||||
appList: [] as any[],
|
||||
userInfo: null as any,
|
||||
modList: [] as any[],
|
||||
isAlone: false,
|
||||
});
|
||||
|
||||
function roleOfPath(p: string): "teacher" | "student" | "parent" {
|
||||
if (p.startsWith("/stu")) return "student";
|
||||
if (p.startsWith("/par")) return "parent";
|
||||
return "teacher";
|
||||
}
|
||||
|
||||
async function initShell() {
|
||||
const role = roleOfPath(router.currentRoute.value.path);
|
||||
try {
|
||||
// 端分离:先切换 mock 当前视角,再拉身份与菜单
|
||||
await baseApi.setRole(role).send();
|
||||
} catch {
|
||||
/* 忽略:非 mock 环境可能无此接口 */
|
||||
}
|
||||
try {
|
||||
// 1. 获取当前用户信息
|
||||
const user: any = await baseApi.getUserInfo().send();
|
||||
const { id, name, profilePicture } = user || {};
|
||||
|
||||
// 2. 获取当前用户已授权的模块列表
|
||||
const modList: any[] = await baseApi.getUserMod().send();
|
||||
|
||||
layoutData.userInfo = { id, name: name || "管理员", avatar: profilePicture || "" };
|
||||
layoutData.modList = modList || [];
|
||||
|
||||
// 开发环境打印平台原始模块配置,便于排查菜单路径解析问题
|
||||
if (import.meta.env.DEV) {
|
||||
console.table(
|
||||
(modList || []).map((m: any) => ({
|
||||
name: m.name,
|
||||
id: m.id,
|
||||
modId: m.modId,
|
||||
path: m.page,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
// 有子菜单时不算「单模块独占」模式
|
||||
const hasChildren = (modList || []).some((m: any) => m.children && m.children.length > 0);
|
||||
layoutData.isAlone = (modList || []).length <= 1 && !hasChildren;
|
||||
|
||||
isLogin.value = true;
|
||||
|
||||
// 登录后跳转第一个可用模块("/" 本身即已收到页,无需跳转)
|
||||
if (router.currentRoute.value.path === "/") {
|
||||
const firstModule = (modList || [])[0];
|
||||
const firstRaw = firstModule?.children?.length
|
||||
? firstModule.children[0]
|
||||
: firstModule;
|
||||
const first = resolveMenuPage(firstRaw?.page, firstRaw?.modId ?? firstRaw?.id, firstRaw?.name);
|
||||
if (!first.external && first.path && first.path !== "/") {
|
||||
router.replace(first.path);
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error("加载用户信息失败", error);
|
||||
if (error?.message !== "redirect") {
|
||||
Message.error("载入用户信息失败,请检查后端服务是否正常!");
|
||||
}
|
||||
// 降级:使用默认值("/" 即已收到页,无需跳转)
|
||||
layoutData.userInfo = { name: "管理员", avatar: "" };
|
||||
layoutData.modList = [];
|
||||
layoutData.isAlone = true;
|
||||
isLogin.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(initShell);
|
||||
|
||||
// 端分离:路由跨角色前缀(/stu、/par、其它)切换时,重新设定视角并初始化壳层
|
||||
watch(
|
||||
() => router.currentRoute.value.path,
|
||||
(p, prev) => {
|
||||
if (roleOfPath(p) !== roleOfPath(prev || "")) initShell();
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<layout v-if="isLogin" :layoutData="layoutData" />
|
||||
<div v-if="isLogin" class="base-wrap-main" :class="{ 'alone-page': layoutData.isAlone }">
|
||||
<div class="main-wrapper">
|
||||
<router-view />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
#app {
|
||||
height: 100%;
|
||||
}
|
||||
/* 页面级滚动容器:main-wrapper 只有 min-height 时内容超一屏会被 body 的
|
||||
overflow:hidden 直接裁掉(如发作业页),无任何滚动条。定高 + 纵向滚动,
|
||||
放得下的列表页(表格高度由 Utils.useTableHeight 算好)不受影响 */
|
||||
.base-wrap-main .main-wrapper {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
/* 表格操作列(operatingHandle 渲染的 Tooltip 包裹层)横排且随单元格居中 */
|
||||
.ivu-table-cell > .ivu-tooltip {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin: 0 2px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user