diff --git a/src/api/teacher.js b/src/api/teacher.js index ef5afa8..7822217 100644 --- a/src/api/teacher.js +++ b/src/api/teacher.js @@ -485,6 +485,30 @@ export function addStudent(params) { params, }); } +export function addTeacherRosterStudent(data) { + return request({ + url: "/api/user/teacher-roster/students", + method: "POST", + params: { + name: data.name, + userName: data.userName, + teachingClassId: data.teachingClassId, + operatorId: data.operatorId, + }, + }); +} +export function updateTeacherRosterStudent(data) { + return request({ + url: `/api/user/teacher-roster/students/${data.userId}`, + method: "POST", + params: { + name: data.name, + userName: data.userName, + teachingClassId: data.teachingClassId, + operatorId: data.operatorId, + }, + }); +} // 学生管理 编辑 export function updateStudent(data) { const { operatorId, teachingClassId, ...body } = data; diff --git a/src/assets/styles/element-ui.scss b/src/assets/styles/element-ui.scss index 4a1a930..8ab8f1a 100644 --- a/src/assets/styles/element-ui.scss +++ b/src/assets/styles/element-ui.scss @@ -95,15 +95,15 @@ color: var(--el-color-primary) !important; } -.app-container .el-table .el-loading-mask { - background: rgba(4, 18, 29, 0.72) !important; - backdrop-filter: blur(1px); +.el-loading-mask { + background: rgba(4, 18, 29, 0.78) !important; + backdrop-filter: blur(2px); } -.app-container .el-table .el-loading-spinner .path { +.el-loading-spinner .path { stroke: #20d6ff; } -.app-container .el-table .el-loading-text { +.el-loading-text { color: #d7f8ff; } diff --git a/src/layout/components/AppMain.vue b/src/layout/components/AppMain.vue index acb4823..d7ea4d7 100644 --- a/src/layout/components/AppMain.vue +++ b/src/layout/components/AppMain.vue @@ -33,7 +33,14 @@ const router = useRouter(); const role = ref(JSON.parse(getUserInfo()).roleId); const isDemo = computed(() => isStudentDemo()); const demoClassName = computed(() => getStudentDemoSession()?.userInfo?.className || "教学班"); -function exitDemo() { clearStudentDemoSession(); window.location.replace("/student-demo"); } +function exitDemo() { + clearStudentDemoSession(); + window.close(); + // 仅在浏览器不允许关闭窗口时兜底,避免停留在演示会话中转页。 + window.setTimeout(() => { + if (!window.closed) window.location.replace("/index"); + }, 100); +} const mainClass = computed(() => { if (Number(role.value) === 4) return "app-main"; if (Number(role.value) === 3) return "teacher-main"; @@ -314,6 +321,7 @@ router.beforeEach(async (to, from, next) => { --el-table-border-color: rgba(54, 159, 212, 0.2); --el-table-header-bg-color: rgba(9, 48, 73, 0.96); --el-table-tr-bg-color: rgba(2, 16, 27, 0.78); + --el-fill-color-lighter: rgba(2, 16, 27, 0.72); --el-table-row-hover-bg-color: rgba(0, 111, 177, 0.18); margin-top: 16px !important; color: #dff4ff; diff --git a/src/layout/components/Navbar.vue b/src/layout/components/Navbar.vue index 384904f..b702ca7 100644 --- a/src/layout/components/Navbar.vue +++ b/src/layout/components/Navbar.vue @@ -78,9 +78,10 @@ import HeaderSearch from "@/components/HeaderSearch"; import RuoYiGit from "@/components/RuoYi/Git"; import RuoYiDoc from "@/components/RuoYi/Doc"; import useAppStore from "@/store/modules/app"; -import useUserStore from "@/store/modules/user"; -import useSettingsStore from "@/store/modules/settings"; -import * as indexApi from "@/api/teacher"; +import useUserStore from "@/store/modules/user"; +import useSettingsStore from "@/store/modules/settings"; +import * as indexApi from "@/api/teacher"; +import { getStudentDemoSession, isStudentDemo } from "@/utils/studentDemo"; const { proxy } = getCurrentInstance(); const router = useRouter(); const appStore = useAppStore(); @@ -275,6 +276,19 @@ const roleList = computed(() => { }); // 获取用户详情 const getUserInfo = () => { + if (isStudentDemo()) { + const demoUser = getStudentDemoSession()?.userInfo || userStore.userInfo; + form.value = { + name: demoUser.name || "演示学生", + username: demoUser.username || "", + schoolFacultyId: "演示模式", + schoolMajorId: "演示模式", + schoolClassId: demoUser.className || "教学班", + password: "******", + passwords: "", + }; + return; + } if (role.value === 1) { form.value = { name: userStore.userInfo.name, @@ -295,8 +309,12 @@ const getUserInfo = () => { .catch(() => {}); }; // 修改密码 -const changePassword = () => { - indexApi +const changePassword = () => { + if (isStudentDemo()) { + proxy.$modal.msgWarning("演示模式不支持修改密码"); + return; + } + indexApi .updatePassword({ userId: userStore.userInfo.userId, password: form.value.passwords }) .then(() => { proxy.$modal.msgSuccess("修改成功"); diff --git a/src/layout/components/Sidebar/index.vue b/src/layout/components/Sidebar/index.vue index ac7f03e..8acd820 100644 --- a/src/layout/components/Sidebar/index.vue +++ b/src/layout/components/Sidebar/index.vue @@ -52,8 +52,8 @@ - - + + @@ -76,9 +76,11 @@ const route = useRoute(); const router = useRouter(); const appStore = useAppStore(); const settingsStore = useSettingsStore(); -const permissionStore = usePermissionStore(); -const sidebarRouters = ref([...constantRoutes]); -const routerStatus = { +const permissionStore = usePermissionStore(); +const sidebarRouters = ref([...constantRoutes]); +const visibleSidebarRouters = computed(() => filterRoutesByOrganizationMode(sidebarRouters.value)); +const sidebarMenuKey = computed(() => visibleSidebarRouters.value.map((route) => route.path).join("|")); +const routerStatus = { 行业需求分析: [ { id: "9f413263-e6ff-4ee5-984a-7a231cad6818", @@ -337,8 +339,8 @@ const teacherManageNav = [ { title: "教学管理", children: [ - { title: "院系管理", path: "/school/index" }, - { title: "专业管理", path: "/major/index" }, + { title: "院系管理", path: "/school/index", requiresFacultyManagement: true }, + { title: "专业管理", path: "/major/index", requiresMajorManagement: true }, { title: "班级管理", path: "/class/index" }, { title: "教师管理", path: "/teacher/index" }, { title: "学生管理", path: "/student/index" }, @@ -374,7 +376,7 @@ const trainingTaskKeyByPath = { "/assessment/diagnosis": "product-diagnosis-iteration", }; const trainingTaskTitleByPath = ref({}); -const currentCourseNav = computed(() => (isTeacherRole.value ? teacherManageNav : studentCourseNav)); +const currentCourseNav = computed(() => (isTeacherRole.value ? filterTeacherManageNavigation(teacherManageNav) : studentCourseNav)); const studentIconByPath = { "/assistant/index": "MagicStick", "/foundation/new-product-survey": "Search", @@ -561,10 +563,11 @@ function excludeRoutesByMetaTitle(routes, excludeTitles) { return result; }, []); } -const getTaskList = () => { +const getTaskList = async () => { if (userStore.userInfo.roleId == 1) { sidebarRouters.value = [...constantRoutes, ...platformAdminRoutes]; } else if (userStore.userInfo.roleId == 2) { + await userStore.ensureSchoolProductConfig().catch(() => null); sidebarRouters.value = [...constantRoutes, ...filterRoutesByOrganizationMode(schoolAdminRoutes)]; } else if (userStore.userInfo.roleId != 3) { indexApi.getStudentTaskList({ userId: userStore.userInfo.userId }).then((res) => { @@ -572,26 +575,41 @@ const getTaskList = () => { console.log(processModuleStatus(groupAndSortModules(res.data))); proxy.$cache.session.setJSON("disabledChildren", processModuleStatus(groupAndSortModules(res.data)).disabledChildren); proxy.$cache.session.setJSON("disabledTitles", disabledTitles); - sidebarRouters.value = [...constantRoutes, ...dynamicRoutes]; - }); + sidebarRouters.value = [...constantRoutes, ...dynamicRoutes]; + }); } else { + await userStore.ensureSchoolProductConfig().catch(() => null); sidebarRouters.value = [...constantRoutes, ...filterRoutesByOrganizationMode(teacherRoutes)]; } }; -function filterRoutesByOrganizationMode(routes) { +function isSchoolNavigationVisible(item) { const config = userStore.schoolProductConfig; - const legacyAdminEnabled = config?.organizationMode !== "TEACHING_CLASS_ONLY"; - const facultyEnabled = config?.facultyManagementEnabled ?? legacyAdminEnabled; - const majorEnabled = config?.majorManagementEnabled ?? legacyAdminEnabled; - const adminClassEnabled = config?.adminClassManagementEnabled ?? legacyAdminEnabled; - return routes.filter((route) => { - if (route.meta?.requiresManagerTeacher && userStore.userInfo.teacherAdmin !== true) return false; - if (route.meta?.requiresFacultyManagement && !facultyEnabled) return false; - if (route.meta?.requiresMajorManagement && !majorEnabled) return false; - if (route.meta?.requiresAdminClass && !adminClassEnabled) return false; - return true; - }); + const facultyEnabled = config?.facultyManagementEnabled === true; + const majorEnabled = config?.majorManagementEnabled === true; + const adminClassEnabled = config?.adminClassManagementEnabled === true; + const capability = item.meta || item; + if (capability.requiresManagerTeacher && userStore.userInfo.teacherAdmin !== true) return false; + if (capability.requiresFacultyManagement && !facultyEnabled) return false; + if (capability.requiresMajorManagement && !majorEnabled) return false; + if (capability.requiresAdminClass && !adminClassEnabled) return false; + return true; +} + +function filterRoutesByOrganizationMode(routes) { + return routes.filter(isSchoolNavigationVisible); +} + +function filterTeacherManageNavigation(sections) { + return sections + .map((section) => { + if (!section.children) return section; + return { + ...section, + children: section.children.filter(isSchoolNavigationVisible), + }; + }) + .filter((section) => (section.children ? section.children.length > 0 : isSchoolNavigationVisible(section))); } watch( () => userStore.schoolProductConfig?.organizationMode, diff --git a/src/router/index.js b/src/router/index.js index c32a9b5..cb3a37f 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -511,7 +511,7 @@ export const teacherRoutes = [ component: Layout, redirect: "/teacher/index", roles: ["teacher"], - meta: { title: "教师管理", icon: "教师管理", affix: true, requiresAdminClass: true }, + meta: { title: "教师管理", icon: "教师管理", affix: true }, children: [ // 教师管理 { diff --git a/src/store/modules/user.js b/src/store/modules/user.js index ebf5aff..e5328a2 100644 --- a/src/store/modules/user.js +++ b/src/store/modules/user.js @@ -68,7 +68,7 @@ const useUserStore = defineStore("user", { if (this.schoolProductConfig) { return Promise.resolve(this.schoolProductConfig); } - if (!this.userInfo?.schoolId || this.userInfo?.roleId == 1) { + if (this.userInfo?.roleId == 1) { return Promise.resolve(null); } return getCurrentSchoolProductConfig().then((res) => { diff --git a/src/views/login.vue b/src/views/login.vue index d5d6e6f..3509e6d 100644 --- a/src/views/login.vue +++ b/src/views/login.vue @@ -179,8 +179,8 @@ getCookie(); -webkit-text-fill-color: #303133; } - :deep(.password-input .el-input__inner::placeholder), - :deep(.password-input input::placeholder) { + :deep(.el-input__inner::placeholder), + :deep(input::placeholder) { color: #b8c0cc; -webkit-text-fill-color: #b8c0cc; opacity: 1; diff --git a/src/views/teacherEnd/class/index.vue b/src/views/teacherEnd/class/index.vue index 8cde940..cfbad32 100644 --- a/src/views/teacherEnd/class/index.vue +++ b/src/views/teacherEnd/class/index.vue @@ -23,12 +23,12 @@ - + - + diff --git a/src/views/teacherEnd/student/index.vue b/src/views/teacherEnd/student/index.vue index b35c7f9..81cd6cb 100644 --- a/src/views/teacherEnd/student/index.vue +++ b/src/views/teacherEnd/student/index.vue @@ -77,7 +77,8 @@ - + + + + + + + @@ -121,6 +128,7 @@ import useUserStore from "@/store/modules/user"; const userStore = useUserStore(); const currentUserId = computed(() => userStore.userInfo.userId); const currentSchoolId = computed(() => userStore.userInfo.schoolId); +const isTeacherRosterMode = computed(() => userStore.schoolProductConfig?.studentRosterOwner === "TEACHER"); import * as indexApi from "@/api/teacher"; const dialogVisible = ref(false); const loading = ref(false); @@ -130,6 +138,7 @@ const formInline = ref({ schoolMajorId: "", schoolMajorName: "", schoolClassId: "", + teachingClassId: "", schoolName: "", userName: "", }); @@ -139,17 +148,25 @@ const createEmptyForm = () => ({ schoolMajorId: "", schoolMajorName: "", schoolClassId: "", + teachingClassId: "", schoolName: "", userName: "", name: "", }); -const rules = ref({ +const organizationRules = { schoolFacultyId: [{ required: true, message: "请选择所属院系", trigger: "change" }], schoolMajorId: [{ required: true, message: "请选择所属专业", trigger: "change" }], schoolClassId: [{ required: true, message: "请选择所属班级", trigger: "change" }], +}; +const studentIdentityRules = { name: [{ required: true, message: "请输入学生姓名", trigger: "blur" }], userName: [{ required: true, message: "请输入学号", trigger: "blur" }], -}); +}; +const teacherRosterRules = { + ...studentIdentityRules, + teachingClassId: [{ required: true, message: "请选择所属教学班", trigger: "change" }], +}; +const studentFormRules = computed(() => (isTeacherRosterMode.value ? teacherRosterRules : { ...organizationRules, ...studentIdentityRules })); const paramsquery = ref({ index: 1, size: 10, @@ -194,18 +211,30 @@ const editSchool = (row) => { let data = { ...row }; formInline.value = data; formInline.value.userName = row.username; + formInline.value.teachingClassId = isTeacherRosterMode.value ? (row.teachingClassId || currentTeachingClassId()) : ""; classInfo.value = { className: row.className || row.schoolClassName || "", }; dialogVisible.value = true; status.value = false; - indexApi.getFacultyAndMajorName({ schoolMajorId: row.schoolMajorId }).then((res) => { - classInfo.value.name = Object.keys(res.data)[0]; - classInfo.value.value = Object.values(res.data)[0]; - }); + if (!isTeacherRosterMode.value && row.schoolMajorId) { + indexApi.getFacultyAndMajorName({ schoolMajorId: row.schoolMajorId }).then((res) => { + classInfo.value.name = Object.keys(res.data)[0]; + classInfo.value.value = Object.values(res.data)[0]; + }); + } }; const add = () => { formInline.value.schoolId = currentSchoolId.value; + formInline.value.operatorId = currentUserId.value; + if (isTeacherRosterMode.value) { + indexApi.addTeacherRosterStudent(formInline.value).then((res) => { + proxy.$message.success("添加成功"); + dialogVisible.value = false; + getList(); + }); + return; + } formInline.value.schoolMajorName = majorList.value?.filter((item) => item.schoolMajorId == formInline.value.schoolMajorId)[0].schoolMajorName; formInline.value.schoolClassName = classList1.value?.filter((item) => item.schoolClassId == formInline.value.schoolClassId)[0].className; formInline.value.userId = currentUserId.value; @@ -218,6 +247,14 @@ const add = () => { const edit = () => { formInline.value.status = true; formInline.value.operatorId = currentUserId.value; + if (isTeacherRosterMode.value) { + indexApi.updateTeacherRosterStudent(formInline.value).then((res) => { + proxy.$message.success("编辑成功"); + dialogVisible.value = false; + getList(); + }); + return; + } formInline.value.teachingClassId = currentTeachingClassId(); indexApi.updateStudent(formInline.value).then((res) => { proxy.$message.success("编辑成功"); @@ -352,6 +389,14 @@ const canUseAdminClassForStudent = (schoolClass) => { }; const classMap = computed(() => new Map(classList.value.map((item) => [item.schoolClassId, item]))); const adminClassList = computed(() => classList.value.filter((item) => canUseAdminClassForStudent(item))); +const teachingClassOptions = computed(() => + classList.value.filter( + (schoolClass) => + normalizeClassType(schoolClass) === "TEACHING" && + sameValue(schoolClass.schoolId, currentSchoolId.value) && + sameValue(schoolClass.createdBy, currentUserId.value) + ) +); const canOperateStudent = (row) => { const selectedClass = classMap.value.get(paramsquery.value.schoolClassId); if (selectedClass && normalizeClassType(selectedClass) === "TEACHING") { @@ -424,10 +469,13 @@ const initPassword = (row, status) => { .catch(() => {}); } }; -onMounted(() => { +onMounted(async () => { + await userStore.ensureSchoolProductConfig().catch(() => null); getList(); getClassList(); - getFacultyList(); + if (!isTeacherRosterMode.value) { + getFacultyList(); + } });