diff --git a/src/api/teacher.js b/src/api/teacher.js
index e09de81..7f2b91a 100644
--- a/src/api/teacher.js
+++ b/src/api/teacher.js
@@ -338,6 +338,30 @@ export function addClass(params) {
});
}
// 班级管理-删除
+export function uploadTeachingClassStudentMembers(data) {
+ return request({
+ url: "/api/user/uploadTeachingClassStudentMembers",
+ method: "POST",
+ data,
+ headers: { "Content-Type": "multipart/form-data" },
+ });
+}
+export function importTeacherStudents(data) {
+ return request({
+ url: "/api/user/importTeacherStudents",
+ method: "POST",
+ data,
+ headers: { "Content-Type": "multipart/form-data" },
+ });
+}
+export function uploadTeachingClassByStudentMembers(data) {
+ return request({
+ url: "/api/user/uploadTeachingClassByStudentMembers",
+ method: "POST",
+ data,
+ headers: { "Content-Type": "multipart/form-data" },
+ });
+}
export function deleteClass(params) {
return request({
url: "/api/user/deleteSchoolClass",
@@ -418,13 +442,33 @@ export function getStudentList(query) {
});
}
// 根据学校id查询班级
-export function getClassListBySchoolId(query) {
- return request({
+const classListBySchoolIdCache = new Map();
+const CLASS_LIST_CACHE_TTL = 30 * 1000;
+
+export function clearClassListBySchoolIdCache(schoolId) {
+ if (schoolId) {
+ classListBySchoolIdCache.delete(schoolId);
+ return;
+ }
+ classListBySchoolIdCache.clear();
+}
+
+export function getClassListBySchoolId(query, options = {}) {
+ const schoolId = query?.schoolId || "";
+ const cached = classListBySchoolIdCache.get(schoolId);
+ const now = Date.now();
+ if (!options.force && cached && now - cached.time < CLASS_LIST_CACHE_TTL) {
+ return cached.promise;
+ }
+ const promise = request({
url: "/api/user/seleteSchoolClassListBySchoolId",
method: "POST",
params: query,
});
+ classListBySchoolIdCache.set(schoolId, { time: now, promise });
+ return promise;
}
+
// 根据专业id查询班级
export function getClassListByMajorId(query) {
return request({
@@ -443,12 +487,14 @@ export function addStudent(params) {
}
// 学生管理 编辑
export function updateStudent(data) {
+ const { operatorId, teachingClassId, ...body } = data;
return request({
url: "/api/user/updateStudent",
method: "POST",
- data,
+ data: body,
params: {
- operatorId: data.operatorId,
+ operatorId,
+ teachingClassId,
},
});
}
@@ -478,6 +524,20 @@ export function getTaskList(query) {
params: query,
});
}
+export function getStudentTaskList(query) {
+ return request({
+ url: "/api/taskAllocation/selectTaskAllocationByStudentUserId",
+ method: "POST",
+ params: query,
+ });
+}
+export function getCurrentTeachingClass(query) {
+ return request({
+ url: "/api/user/selectCurrentTeachingClass",
+ method: "POST",
+ params: query,
+ });
+}
// 任务分配
export function addTask(data) {
let { taskAllocationList, classId, schoolId, userId } = data;
diff --git a/src/views/teacherEnd/class/index.vue b/src/views/teacherEnd/class/index.vue
index 90702c5..fce376f 100644
--- a/src/views/teacherEnd/class/index.vue
+++ b/src/views/teacherEnd/class/index.vue
@@ -17,7 +17,7 @@
刷新
-
+
新增{{ activeClassType === 'ADMIN' ? '行政班' : '教学班' }}
@@ -33,9 +33,9 @@
{{ row.schoolMajorName || "-" }}
-
+
- {{ row.schoolClass.createdByName || row.schoolClass.createdBy || "-" }}
+ {{ row.createdByName || "-" }}
@@ -50,16 +50,21 @@
{{ formatDate(row.schoolClass.createTime) }}
-
+
-
- 编辑
- 删除
-
- 数据初始化
+
+
+ 成员管理
-
- 仅查看
+
+ 编辑
+ 删除
+
+ 数据初始化
+
+
+ 仅查看
+
@@ -69,18 +74,37 @@
-
+
-
+
+
+
+
+
+
+
+
+
+ 导入名单创建
+
+ 模板下载
+
+
+
+
+
+
+
+ 查询
+ 刷新
+
+
+ 导入成员
+
+ 模板下载
+
+
+
+
+
+
+
+
+
+
+
+
+ 移除
+ 数据初始化
+
+ 仅查看
+
+
+
+
+
@@ -104,17 +173,24 @@ const userStore = useUserStore();
const activeClassType = ref("ADMIN");
const dialogVisible = ref(false);
+const memberDialogVisible = ref(false);
const loading = ref(false);
+const memberLoading = ref(false);
const isCreate = ref(true);
const tableData = ref([]);
+const memberList = ref([]);
const schoolTotal = ref(0);
+const memberTotal = ref(0);
const facultyList = ref([]);
const majorList = ref([]);
const majorListForForm = ref([]);
+const allClassList = ref([]);
+const sourceAdminClassIds = ref([]);
+const selectedTeachingClass = ref(null);
const currentUserId = computed(() => userStore.userInfo.userId);
const currentSchoolId = computed(() => userStore.userInfo.schoolId);
-const isManagerTeacher = computed(() => userStore.userInfo.teacherAdmin === true || String(userStore.userInfo.roleId) === "1");
+const canCreateCurrentClass = computed(() => activeClassType.value === "ADMIN" || activeClassType.value === "TEACHING");
const paramsquery = ref({
index: 1,
@@ -126,6 +202,7 @@ const paramsquery = ref({
});
const formInline = ref(defaultForm());
+const memberParams = ref(defaultMemberParams());
const rules = {
schoolFacultyId: [{ required: true, message: "请选择所属院系", trigger: "change" }],
@@ -140,11 +217,55 @@ const tableHeaderStyle = {
};
const dialogTitle = computed(() => `${isCreate.value ? "新增" : "编辑"}${activeClassType.value === "ADMIN" ? "行政班" : "教学班"}`);
+const memberDialogTitle = computed(() => `${selectedTeachingClass.value?.className || ""} 成员管理`);
+const selectedTeachingClassCanOperate = computed(() => canOperateClass(selectedTeachingClass.value));
const filteredTableData = computed(() => {
return tableData.value.filter((row) => normalizeClassType(row.schoolClass) === activeClassType.value);
});
+const adminClassOptions = computed(() => {
+ return allClassList.value.filter((schoolClass) => {
+ return normalizeClassType(schoolClass) === "ADMIN"
+ && (!formInline.value.schoolMajorId || schoolClass.schoolMajorId === formInline.value.schoolMajorId);
+ });
+});
+
+const importCreateUploadData = computed(() => ({
+ schoolId: currentSchoolId.value,
+ schoolFacultyId: formInline.value.schoolFacultyId,
+ schoolMajorId: formInline.value.schoolMajorId,
+ className: formInline.value.className,
+ operatorId: currentUserId.value,
+ sourceClassIds: sourceAdminClassIds.value.join(","),
+}));
+
+const memberUploadData = computed(() => ({
+ teachingClassId: selectedTeachingClass.value?.schoolClassId || "",
+ operatorId: currentUserId.value,
+}));
+
+function isMultiTeachingClassConflict(error) {
+ return error?.response?.data?.code === 409;
+}
+
+function multiTeachingClassMessage(error) {
+ const body = error?.response?.data || {};
+ const students = Array.isArray(body.data) ? body.data : [];
+ const details = students.length ? `\n${students.slice(0, 12).join("、")}${students.length > 12 ? " 等" : ""}` : "";
+ return `${body.msg || "部分学生已在其他教学班,是否继续操作?"}${details}`;
+}
+
+function buildUploadFormData(file, data, confirmMultiTeachingClass = false) {
+ const formData = new FormData();
+ formData.append("file", file);
+ Object.keys(data).forEach((key) => {
+ formData.append(key, data[key] ?? "");
+ });
+ formData.append("confirmMultiTeachingClass", confirmMultiTeachingClass);
+ return formData;
+}
+
function defaultForm() {
return {
schoolFacultyId: "",
@@ -156,6 +277,17 @@ function defaultForm() {
};
}
+function defaultMemberParams() {
+ return {
+ index: 1,
+ size: 10,
+ schoolId: currentSchoolId.value,
+ schoolClassId: "",
+ name: "",
+ userName: "",
+ };
+}
+
function normalizeClassType(schoolClass) {
if (schoolClass?.classType) {
return schoolClass.classType;
@@ -167,14 +299,17 @@ function canOperateClass(schoolClass) {
if (schoolClass?.dataSource === "ZHIYUN") {
return false;
}
- if (normalizeClassType(schoolClass) === "TEACHING") {
- return schoolClass?.createdBy === currentUserId.value;
+ if (normalizeClassType(schoolClass) === "ADMIN") {
+ return !schoolClass?.schoolId || schoolClass.schoolId === currentSchoolId.value;
}
- return isManagerTeacher.value || schoolClass?.createdBy === currentUserId.value;
+ return schoolClass?.createdBy === currentUserId.value;
}
-function getList() {
- loading.value = true;
+function getList(options = {}) {
+ const { showLoading = true } = options;
+ if (showLoading) {
+ loading.value = true;
+ }
indexApi
.getClassesList(paramsquery.value)
.then((res) => {
@@ -182,7 +317,9 @@ function getList() {
schoolTotal.value = res.data?.total || 0;
})
.finally(() => {
- loading.value = false;
+ if (showLoading) {
+ loading.value = false;
+ }
});
}
@@ -192,33 +329,59 @@ function handleTabChange() {
}
function openCreateDialog() {
+ if (!canCreateCurrentClass.value) {
+ proxy.$message.warning("普通教师不能新增行政班");
+ return;
+ }
isCreate.value = true;
formInline.value = defaultForm();
+ sourceAdminClassIds.value = [];
dialogVisible.value = true;
}
function editClass(row) {
+ const schoolClass = row?.schoolClass || row;
+ const schoolFacultyId = schoolClass?.schoolFacultyId || row?.schoolFacultyId || "";
+ const schoolMajorId = schoolClass?.schoolMajorId || row?.schoolMajorId || "";
isCreate.value = false;
formInline.value = {
- ...row,
- classType: normalizeClassType(row),
+ ...schoolClass,
+ schoolFacultyId,
+ schoolMajorId,
+ classType: normalizeClassType(schoolClass),
userId: currentUserId.value,
};
+ sourceAdminClassIds.value = [];
+ majorListForForm.value = [];
dialogVisible.value = true;
+ hydrateClassFormOptions(formInline.value);
}
-function addClass() {
+function addClass(confirmMultiTeachingClass = false) {
const payload = {
...formInline.value,
schoolId: currentSchoolId.value,
classType: activeClassType.value,
+ sourceClassIds: activeClassType.value === "TEACHING" ? sourceAdminClassIds.value.join(",") : "",
userId: currentUserId.value,
+ confirmMultiTeachingClass,
};
- indexApi.addClass(payload).then(() => {
- proxy.$message.success("添加成功");
- dialogVisible.value = false;
- getList();
- });
+ indexApi
+ .addClass(payload)
+ .then(() => {
+ proxy.$message.success("添加成功");
+ dialogVisible.value = false;
+ getList({ showLoading: false });
+ refreshAllClassList();
+ })
+ .catch((error) => {
+ if (!isMultiTeachingClassConflict(error)) return;
+ ElMessageBox.confirm(multiTeachingClassMessage(error), "学生已在其他教学班", {
+ confirmButtonText: "继续创建",
+ cancelButtonText: "取消",
+ type: "warning",
+ }).then(() => addClass(true));
+ });
}
function updateClass() {
@@ -232,17 +395,249 @@ function updateClass() {
indexApi.updateClass(payload).then(() => {
proxy.$message.success("编辑成功");
dialogVisible.value = false;
- getList();
+ getList({ showLoading: false });
+ refreshAllClassList();
});
}
function submitForm() {
proxy.$refs.formInlineref.validate((valid) => {
if (!valid) return;
+ if (isCreate.value && activeClassType.value === "TEACHING" && sourceAdminClassIds.value.length === 0) {
+ proxy.$message.warning("请选择来源行政班");
+ return;
+ }
isCreate.value ? addClass() : updateClass();
});
}
+async function beforeImportCreate(file) {
+ if (activeClassType.value !== "TEACHING") {
+ proxy.$message.warning("请切换到教学班");
+ return false;
+ }
+ if (!formInline.value.schoolFacultyId || !formInline.value.schoolMajorId || !formInline.value.className) {
+ proxy.$message.warning("请先填写院系、专业和班级名称");
+ return false;
+ }
+ if (sourceAdminClassIds.value.length === 0) {
+ proxy.$message.warning("请选择来源行政班");
+ return false;
+ }
+ const total = await getSelectedSourceAdminStudentTotal();
+ if (total <= 0) {
+ proxy.$message.warning("所选来源行政班暂无学生,请先在学生管理中导入行政班学生后再创建教学班");
+ return false;
+ }
+ if (!/\.(xls|xlsx)$/i.test(file.name)) {
+ proxy.$message.warning("请选择xls或xlsx格式文件");
+ return false;
+ }
+ return true;
+}
+
+async function getSelectedSourceAdminStudentTotal() {
+ const results = await Promise.all(sourceAdminClassIds.value.map((schoolClassId) => {
+ return indexApi.getStudentList({
+ index: 1,
+ size: 1,
+ schoolId: currentSchoolId.value,
+ schoolClassId,
+ });
+ }));
+ return results.reduce((sum, res) => sum + Number(res.data?.total || 0), 0);
+}
+
+function uploadImportCreate(options, confirmMultiTeachingClass = false) {
+ const formData = buildUploadFormData(options.file, importCreateUploadData.value, confirmMultiTeachingClass);
+ return indexApi
+ .uploadTeachingClassByStudentMembers(formData)
+ .then((res) => {
+ handleImportCreateSuccess(res);
+ })
+ .catch((error) => {
+ if (!isMultiTeachingClassConflict(error)) return;
+ return ElMessageBox.confirm(multiTeachingClassMessage(error), "学生已在其他教学班", {
+ confirmButtonText: "继续导入创建",
+ cancelButtonText: "取消",
+ type: "warning",
+ }).then(() => uploadImportCreate(options, true));
+ });
+}
+
+function handleImportCreateSuccess(res) {
+ if (res?.statusCode && res.statusCode !== "OK" && res.statusCode !== 200) {
+ proxy.$message.error(res?.message || "导入创建失败");
+ return;
+ }
+ proxy.$message.success("导入创建成功");
+ dialogVisible.value = false;
+ getList();
+ refreshAllClassList();
+}
+
+function openMemberDialog(schoolClass) {
+ selectedTeachingClass.value = schoolClass;
+ memberParams.value = {
+ ...defaultMemberParams(),
+ schoolClassId: schoolClass.schoolClassId,
+ };
+ memberDialogVisible.value = true;
+ getMemberList();
+}
+
+function closeMemberDialog() {
+ selectedTeachingClass.value = null;
+ memberList.value = [];
+ memberTotal.value = 0;
+ memberParams.value = defaultMemberParams();
+}
+
+function getMemberList() {
+ if (!memberParams.value.schoolClassId) {
+ return;
+ }
+ memberLoading.value = true;
+ indexApi
+ .getStudentList(memberParams.value)
+ .then((res) => {
+ memberList.value = res.data?.list || [];
+ memberTotal.value = res.data?.total || 0;
+ })
+ .finally(() => {
+ memberLoading.value = false;
+ });
+}
+
+function refreshMemberList() {
+ memberParams.value.name = "";
+ memberParams.value.userName = "";
+ memberParams.value.index = 1;
+ getMemberList();
+}
+
+function beforeImportMembers(file) {
+ if (!selectedTeachingClass.value?.schoolClassId) {
+ proxy.$message.warning("请先选择教学班");
+ return false;
+ }
+ if (!selectedTeachingClassCanOperate.value) {
+ proxy.$message.warning("只能维护自己创建的教学班成员");
+ return false;
+ }
+ if (!/\.(xls|xlsx)$/i.test(file.name)) {
+ proxy.$message.warning("请选择xls或xlsx格式文件");
+ return false;
+ }
+ return true;
+}
+
+function uploadMemberImport(options, confirmMultiTeachingClass = false) {
+ const formData = buildUploadFormData(options.file, memberUploadData.value, confirmMultiTeachingClass);
+ return indexApi
+ .uploadTeachingClassStudentMembers(formData)
+ .then((res) => {
+ handleMemberImportSuccess(res);
+ })
+ .catch((error) => {
+ if (!isMultiTeachingClassConflict(error)) return;
+ return ElMessageBox.confirm(multiTeachingClassMessage(error), "学生已在其他教学班", {
+ confirmButtonText: "继续导入",
+ cancelButtonText: "取消",
+ type: "warning",
+ }).then(() => uploadMemberImport(options, true));
+ });
+}
+
+function handleMemberImportSuccess(res) {
+ if (res?.statusCode && res.statusCode !== "OK" && res.statusCode !== 200) {
+ proxy.$message.error(res?.message || "导入失败");
+ return;
+ }
+ proxy.$message.success("导入成功");
+ getMemberList();
+}
+
+function removeMember(row) {
+ ElMessageBox.confirm("确定将该学生从当前教学班移除吗?", "提示", {
+ confirmButtonText: "确定",
+ cancelButtonText: "取消",
+ type: "warning",
+ }).then(() => {
+ indexApi
+ .deleteStudent({
+ userId: row.userId,
+ operatorId: currentUserId.value,
+ teachingClassId: selectedTeachingClass.value.schoolClassId,
+ })
+ .then(() => {
+ proxy.$message.success("移除成功");
+ getMemberList();
+ });
+ });
+}
+
+function initializeMember(row) {
+ ElMessageBox.confirm("确定初始化该学生在当前教学班的实训数据吗?该操作不可恢复。", "提示", {
+ confirmButtonText: "确定",
+ cancelButtonText: "取消",
+ type: "warning",
+ }).then(() => {
+ indexApi
+ .initializeTeachingClassStudentTrainingData({
+ teachingClassId: selectedTeachingClass.value.schoolClassId,
+ studentUserId: row.userId,
+ operatorId: currentUserId.value,
+ })
+ .then(() => {
+ proxy.$message.success("初始化成功");
+ });
+ });
+}
+
+async function downloadMemberImportTemplate() {
+ const XLSX = await import("xlsx");
+ const templateSheet = XLSX.utils.aoa_to_sheet([
+ ["所属院系", "所属专业", "行政班", "学号", "学生姓名", "性别", "手机号", "邮箱"],
+ ["商学院", "电子商务", "2026电子商务1班-行政班", "20260001", "张三", "男", "13800000000", "zhangsan@example.com"],
+ ]);
+ templateSheet["!cols"] = [{ wch: 18 }, { wch: 18 }, { wch: 28 }, { wch: 18 }, { wch: 16 }, { wch: 10 }, { wch: 18 }, { wch: 26 }];
+ const guideSheet = XLSX.utils.aoa_to_sheet([
+ ["填写说明"],
+ ["1. 本模板用于向当前教学班追加成员。"],
+ ["2. 不要填写任何数据ID,院系、专业、行政班请填写页面中的名称。"],
+ ["3. 系统优先按当前教学班来源行政班匹配;未配置来源行政班时,按院系、专业、行政班、学号、姓名匹配已有学生。"],
+ ["4. 任意一行校验失败时,整批导入失败并返回失败原因。"],
+ ]);
+ guideSheet["!cols"] = [{ wch: 72 }];
+ const workbook = XLSX.utils.book_new();
+ XLSX.utils.book_append_sheet(workbook, templateSheet, "教学班成员导入模板");
+ XLSX.utils.book_append_sheet(workbook, guideSheet, "填写说明");
+ XLSX.writeFile(workbook, "教学班成员导入模板.xlsx");
+}
+
+async function downloadTeachingClassImportTemplate() {
+ const XLSX = await import("xlsx");
+ const templateSheet = XLSX.utils.aoa_to_sheet([
+ ["所属院系", "所属专业", "行政班", "学号", "学生姓名", "性别", "手机号", "邮箱"],
+ ["商学院", "电子商务", "2026电子商务1班-行政班", "20260001", "张三", "男", "13800000000", "zhangsan@example.com"],
+ ]);
+ templateSheet["!cols"] = [{ wch: 18 }, { wch: 18 }, { wch: 28 }, { wch: 18 }, { wch: 16 }, { wch: 10 }, { wch: 18 }, { wch: 26 }];
+ const guideSheet = XLSX.utils.aoa_to_sheet([
+ ["填写说明"],
+ ["1. 先在新增教学班弹窗中选择院系、专业并填写班级名称;来源行政班可以不选。"],
+ ["2. 不要填写任何数据ID,院系、专业、行政班请填写页面中的名称。"],
+ ["3. 学生必须已存在于对应行政班;如果行政班暂无学生,请先在学生管理中导入行政班学生。"],
+ ["4. 系统会按模板学生名单加入新教学班;学生若已在其他教学班,将自动转入新教学班。"],
+ ["5. 任意一行校验失败时,整批导入创建失败,教学班和成员关系都不会写入。"],
+ ]);
+ guideSheet["!cols"] = [{ wch: 96 }];
+ const workbook = XLSX.utils.book_new();
+ XLSX.utils.book_append_sheet(workbook, templateSheet, "教学班名单创建模板");
+ XLSX.utils.book_append_sheet(workbook, guideSheet, "填写说明");
+ XLSX.writeFile(workbook, "教学班名单创建模板.xlsx");
+}
+
function deleteClass(id) {
ElMessageBox.confirm("确定删除该班级吗?", "提示", {
confirmButtonText: "确定",
@@ -252,6 +647,7 @@ function deleteClass(id) {
indexApi.deleteClass({ schoolClassId: id, userId: currentUserId.value }).then(() => {
proxy.$message.success("删除成功");
getList();
+ refreshAllClassList();
});
});
}
@@ -279,11 +675,13 @@ function refresh() {
function onClose() {
formInline.value = defaultForm();
+ sourceAdminClassIds.value = [];
}
function getFacultyList() {
- indexApi.seleteSchoolFacultyList({ schoolId: currentSchoolId.value }).then((res) => {
+ return indexApi.seleteSchoolFacultyList({ schoolId: currentSchoolId.value }).then((res) => {
facultyList.value = res.data || [];
+ return facultyList.value;
});
}
@@ -298,19 +696,79 @@ function getMajorList() {
});
}
+function loadMajorListForForm(schoolFacultyId) {
+ if (!schoolFacultyId) {
+ majorListForForm.value = [];
+ return Promise.resolve([]);
+ }
+ return indexApi.getMajorListBySchoolId({ schoolFacultyId }).then((res) => {
+ majorListForForm.value = res.data || [];
+ return majorListForForm.value;
+ });
+}
+
function getMajorListForForm() {
+ sourceAdminClassIds.value = [];
formInline.value.schoolMajorId = "";
- if (!formInline.value.schoolFacultyId) {
- majorListForForm.value = [];
+ loadMajorListForForm(formInline.value.schoolFacultyId);
+}
+
+async function hydrateClassFormOptions(row) {
+ const schoolMajorId = row?.schoolMajorId;
+ if (!schoolMajorId) {
return;
}
- indexApi.getMajorListBySchoolId({ schoolFacultyId: formInline.value.schoolFacultyId }).then((res) => {
- majorListForForm.value = res.data || [];
+ if (row.schoolFacultyId) {
+ await loadMajorListForForm(row.schoolFacultyId);
+ return;
+ }
+ const faculties = facultyList.value.length ? facultyList.value : await getFacultyList();
+ const results = await Promise.all(
+ faculties.map((faculty) =>
+ indexApi
+ .getMajorListBySchoolId({ schoolFacultyId: faculty.schoolFacultyId })
+ .then((res) => ({
+ facultyId: faculty.schoolFacultyId,
+ majors: res.data || [],
+ }))
+ .catch(() => ({
+ facultyId: faculty.schoolFacultyId,
+ majors: [],
+ }))
+ )
+ );
+ const matched = results.find((item) => item.majors.some((major) => major.schoolMajorId === schoolMajorId));
+ if (!matched) {
+ return;
+ }
+ majorListForForm.value = matched.majors;
+ formInline.value = {
+ ...formInline.value,
+ schoolFacultyId: matched.facultyId,
+ schoolMajorId,
+ };
+}
+
+function handleFormMajorChange() {
+ sourceAdminClassIds.value = [];
+}
+
+function getAllClassList() {
+ indexApi.getClassListBySchoolId({ schoolId: currentSchoolId.value }).then((res) => {
+ allClassList.value = res.data || [];
+ });
+}
+
+function refreshAllClassList() {
+ indexApi.clearClassListBySchoolIdCache(currentSchoolId.value);
+ indexApi.getClassListBySchoolId({ schoolId: currentSchoolId.value }, { force: true }).then((res) => {
+ allClassList.value = res.data || [];
});
}
onMounted(() => {
getList();
+ getAllClassList();
getFacultyList();
});
@@ -349,8 +807,46 @@ onMounted(() => {
width: 100%;
}
+ .action-buttons {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ flex-wrap: wrap;
+
+ :deep(.el-button) {
+ margin-left: 0;
+ min-width: 56px;
+ padding: 6px 10px;
+ line-height: 1;
+ }
+ }
+
.form-control {
width: 100%;
}
+
+ .import-create-actions {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ flex-wrap: wrap;
+ }
+
+ .member-toolbar {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 10px;
+ margin-bottom: 12px;
+ }
+
+ .member-query {
+ width: 180px;
+ }
+
+ .member-table {
+ width: 100%;
+ }
}
diff --git a/src/views/teacherEnd/student/index.vue b/src/views/teacherEnd/student/index.vue
index d4eb0a3..aff3eb3 100644
--- a/src/views/teacherEnd/student/index.vue
+++ b/src/views/teacherEnd/student/index.vue
@@ -29,8 +29,13 @@
新增
-
- 导入
+
+ 导入
导出
导入模版下载
@@ -125,7 +130,6 @@ import useUserStore from "@/store/modules/user";
const userStore = useUserStore();
const currentUserId = computed(() => userStore.userInfo.userId);
const currentSchoolId = computed(() => userStore.userInfo.schoolId);
-const isManagerTeacher = computed(() => userStore.userInfo.teacherAdmin === true || String(userStore.userInfo.roleId || userStore.userInfo.role) === "1");
import * as indexApi from "@/api/teacher";
const dialogVisible = ref(false);
const loading = ref(false);
@@ -154,6 +158,12 @@ const paramsquery = ref({
});
const classInfo = ref({});
const schoolTotal = ref(0);
+function buildStudentImportFormData(file) {
+ const formData = new FormData();
+ formData.append("file", file);
+ formData.append("operatorId", currentUserId.value);
+ return formData;
+}
// 新增编辑状态
const status = ref(false);
// 院校list
@@ -167,7 +177,7 @@ const getList = () => {
};
// 删除院校
const delSchool = (id) => {
- indexApi.deleteStudent({ userId: id.userId, operatorId: currentUserId.value }).then((res) => {
+ indexApi.deleteStudent({ userId: id.userId, operatorId: currentUserId.value, teachingClassId: currentTeachingClassId() }).then((res) => {
proxy.$message.success("删除成功");
getList();
});
@@ -202,6 +212,7 @@ const add = () => {
const edit = () => {
formInline.value.status = true;
formInline.value.operatorId = currentUserId.value;
+ formInline.value.teachingClassId = currentTeachingClassId();
indexApi.updateStudent(formInline.value).then((res) => {
proxy.$message.success("编辑成功");
dialogVisible.value = false;
@@ -248,7 +259,17 @@ const getMajorList = () => {
majorList.value = res.data;
});
};
+const uploadStudents = (options) => {
+ const formData = buildStudentImportFormData(options.file);
+ return indexApi.importTeacherStudents(formData).then((res) => {
+ handleSuccess(res);
+ });
+};
const handleSuccess = (res, file) => {
+ if (res?.statusCode && res.statusCode !== "OK" && res.statusCode !== 200) {
+ proxy.$message.error(res?.message || "导入失败");
+ return;
+ }
getList();
proxy.$modal.msgSuccess("导入成功");
};
@@ -265,15 +286,54 @@ const exportTeacher = () => {
});
};
// 导入模版下载
-const downloadTemplate = () => {
- const a = document.createElement("a");
- a.href = `http://118.31.7.2:147/file/学生信息表.xlsx`;
- a.download = "模板文件.xlsx";
- document.body.appendChild(a);
- a.click();
- document.body.removeChild(a);
+const buildOwnedAdminClassRows = async () => {
+ const majorGroups = await Promise.all(
+ facultyList.value.map((faculty) =>
+ indexApi
+ .getMajorListBySchoolId({ schoolFacultyId: faculty.schoolFacultyId })
+ .then((res) => (res.data || []).map((major) => ({ ...major, schoolFacultyName: faculty.schoolFacultyName })))
+ .catch(() => [])
+ )
+ );
+ const allMajors = majorGroups.flat();
+ return classList.value
+ .filter((item) => canUseAdminClassForStudent(item))
+ .map((item) => {
+ const major = allMajors.find((majorItem) => majorItem.schoolMajorId === item.schoolMajorId) || majorList.value.find((majorItem) => majorItem.schoolMajorId === item.schoolMajorId);
+ const faculty = facultyList.value.find((facultyItem) => facultyItem.schoolFacultyId === (major?.schoolFacultyId || item.schoolFacultyId));
+ return [faculty?.schoolFacultyName || item.schoolFacultyName || "", major?.schoolMajorName || item.schoolMajorName || "", item.className || ""];
+ })
+ .filter((row) => row.every(Boolean));
+};
+
+const downloadTemplate = async () => {
+ const XLSX = await import("xlsx");
+ const adminClassRows = await buildOwnedAdminClassRows();
+ const firstAdminClass = adminClassRows[0] || ["电子商务学院", "电子商务", "行政一班"];
+ const templateSheet = XLSX.utils.aoa_to_sheet([
+ ["姓名", "学号", "性别", "所属院系", "所属专业", "所属班级", "手机", "邮箱"],
+ ["张三", "20260001", "男", firstAdminClass[0], firstAdminClass[1], firstAdminClass[2], "13800000000", "student@example.com"],
+ ]);
+ templateSheet["!cols"] = [{ wch: 14 }, { wch: 18 }, { wch: 10 }, { wch: 24 }, { wch: 24 }, { wch: 20 }, { wch: 16 }, { wch: 28 }];
+ const dataRows = adminClassRows.length ? adminClassRows : [["暂无可导入行政班", "请先维护专业", "请先创建行政班"]];
+ const dataSheet = XLSX.utils.aoa_to_sheet([["所属院系", "所属专业", "所属班级"], ...dataRows]);
+ dataSheet["!cols"] = [{ wch: 28 }, { wch: 28 }, { wch: 24 }];
+ const guideSheet = XLSX.utils.aoa_to_sheet([
+ ["填写说明"],
+ ["1. 本模板用于学生账号导入,不要填写任何数据ID。"],
+ ["2. 所属院系、所属专业、所属班级请使用基础数据页中的名称。"],
+ ["3. 只能导入本校行政班,不能导入教学班或其他学校的班级。"],
+ ["4. 性别可填写男、女,也可留空。"],
+ ["5. 任意一行校验失败时,整批导入失败并返回失败原因。"],
+ ]);
+ guideSheet["!cols"] = [{ wch: 76 }];
+ const workbook = XLSX.utils.book_new();
+ XLSX.utils.book_append_sheet(workbook, templateSheet, "学生导入模板");
+ XLSX.utils.book_append_sheet(workbook, dataSheet, "基础数据");
+ XLSX.utils.book_append_sheet(workbook, guideSheet, "填写说明");
+ XLSX.writeFile(workbook, "学生账号导入模板.xlsx");
};
-// 查询班级下拉
+
const classList = ref([]);
const normalizeClassType = (schoolClass) => {
if (schoolClass?.classType) return schoolClass.classType;
@@ -282,18 +342,29 @@ const normalizeClassType = (schoolClass) => {
const sameValue = (left, right) => String(left || "") === String(right || "");
const canUseAdminClassForStudent = (schoolClass) => {
if (normalizeClassType(schoolClass) !== "ADMIN") return false;
- if (isManagerTeacher.value) {
- return !schoolClass?.schoolId || sameValue(schoolClass.schoolId, currentSchoolId.value);
- }
- return sameValue(schoolClass?.createdBy, currentUserId.value);
+ return !schoolClass?.schoolId || sameValue(schoolClass.schoolId, currentSchoolId.value);
};
+const classMap = computed(() => new Map(classList.value.map((item) => [item.schoolClassId, item])));
const teachingClassList = computed(() => classList.value.filter((item) => normalizeClassType(item) === "TEACHING"));
const classOwnerMap = computed(() => {
return new Map(classList.value.map((item) => [item.schoolClassId, item.createdBy]));
});
const canOperateStudent = (row) => {
- const teachingClassId = paramsquery.value.schoolClassId || row.schoolClassId;
- return sameValue(classOwnerMap.value.get(teachingClassId), currentUserId.value);
+ const teachingClassId = paramsquery.value.schoolClassId;
+ if (teachingClassId) {
+ return sameValue(classOwnerMap.value.get(teachingClassId), currentUserId.value);
+ }
+ const adminClass = classMap.value.get(row.schoolClassId);
+ return normalizeClassType(adminClass) === "ADMIN"
+ && (!adminClass?.schoolId || sameValue(adminClass.schoolId, currentSchoolId.value));
+};
+const currentTeachingClassId = () => paramsquery.value.schoolClassId || "";
+const beforeImportStudents = (file) => {
+ if (!/\.(xls|xlsx)$/i.test(file.name)) {
+ proxy.$message.warning("请选择xls或xlsx格式文件");
+ return false;
+ }
+ return true;
};
const getClassList = () => {
indexApi.getClassListBySchoolId({ schoolId: userStore.userInfo.schoolId }).then((res) => {
@@ -318,7 +389,7 @@ const initPassword = (row, status) => {
type: "warning",
})
.then(() => {
- indexApi.initStudentPassword({ userId: row.userId, operatorId: currentUserId.value }).then((res) => {
+ indexApi.initStudentPassword({ userId: row.userId, operatorId: currentUserId.value, teachingClassId: currentTeachingClassId() }).then((res) => {
proxy.$message.success("密码初始化成功");
getList();
});