|
|
|
|
@ -17,7 +17,7 @@
|
|
|
|
|
<el-button :icon="Refresh" @click="refresh">刷新</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="table-actions">
|
|
|
|
|
<div v-if="canCreateCurrentClass" class="table-actions">
|
|
|
|
|
<el-button type="primary" :icon="Plus" @click="openCreateDialog">新增{{ activeClassType === 'ADMIN' ? '行政班' : '教学班' }}</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
@ -33,9 +33,9 @@
|
|
|
|
|
<span>{{ row.schoolMajorName || "-" }}</span>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column v-if="activeClassType === 'TEACHING'" label="创建人" align="center">
|
|
|
|
|
<el-table-column label="创建人" align="center">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<span>{{ row.schoolClass.createdByName || row.schoolClass.createdBy || "-" }}</span>
|
|
|
|
|
<span>{{ row.createdByName || "-" }}</span>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="数据来源" align="center" width="120">
|
|
|
|
|
@ -50,16 +50,21 @@
|
|
|
|
|
<span>{{ formatDate(row.schoolClass.createTime) }}</span>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="操作" align="center" width="260">
|
|
|
|
|
<el-table-column label="操作" align="center" width="420">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<template v-if="canOperateClass(row.schoolClass)">
|
|
|
|
|
<el-button type="primary" text @click="editClass(row.schoolClass)">编辑</el-button>
|
|
|
|
|
<el-button type="danger" text @click="deleteClass(row.schoolClass.schoolClassId)">删除</el-button>
|
|
|
|
|
<el-button v-if="activeClassType === 'TEACHING'" type="warning" text @click="initializeClass(row.schoolClass)">
|
|
|
|
|
数据初始化
|
|
|
|
|
<div class="action-buttons">
|
|
|
|
|
<el-button v-if="activeClassType === 'TEACHING'" type="primary" text @click="openMemberDialog(row.schoolClass)">
|
|
|
|
|
成员管理
|
|
|
|
|
</el-button>
|
|
|
|
|
</template>
|
|
|
|
|
<el-tag v-else type="info">仅查看</el-tag>
|
|
|
|
|
<template v-if="canOperateClass(row.schoolClass)">
|
|
|
|
|
<el-button type="primary" text @click="editClass(row)">编辑</el-button>
|
|
|
|
|
<el-button type="danger" text @click="deleteClass(row.schoolClass.schoolClassId)">删除</el-button>
|
|
|
|
|
<el-button v-if="activeClassType === 'TEACHING'" type="warning" text @click="initializeClass(row.schoolClass)">
|
|
|
|
|
数据初始化
|
|
|
|
|
</el-button>
|
|
|
|
|
</template>
|
|
|
|
|
<el-tag v-else type="info">仅查看</el-tag>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
</el-table>
|
|
|
|
|
@ -69,18 +74,37 @@
|
|
|
|
|
<el-dialog v-model="dialogVisible" width="520px" :title="dialogTitle" class="teacher-dialog" @close="onClose">
|
|
|
|
|
<el-form ref="formInlineref" :model="formInline" label-width="96px" :rules="rules">
|
|
|
|
|
<el-form-item label="所属院系" prop="schoolFacultyId">
|
|
|
|
|
<el-select v-model="formInline.schoolFacultyId" placeholder="请选择院系" clearable class="form-control" @change="getMajorListForForm" :disabled="!isCreate">
|
|
|
|
|
<el-select v-model="formInline.schoolFacultyId" placeholder="请选择院系" clearable class="form-control" @change="getMajorListForForm">
|
|
|
|
|
<el-option v-for="faculty in facultyList" :key="faculty.schoolFacultyId" :label="faculty.schoolFacultyName" :value="faculty.schoolFacultyId" />
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="所属专业" prop="schoolMajorId">
|
|
|
|
|
<el-select v-model="formInline.schoolMajorId" placeholder="请选择专业" clearable class="form-control" :disabled="!isCreate">
|
|
|
|
|
<el-select v-model="formInline.schoolMajorId" placeholder="请选择专业" clearable class="form-control" @change="handleFormMajorChange">
|
|
|
|
|
<el-option v-for="major in majorListForForm" :key="major.schoolMajorId" :label="major.schoolMajorName" :value="major.schoolMajorId" />
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item v-if="activeClassType === 'TEACHING' && isCreate" label="来源行政班" prop="sourceClassIds">
|
|
|
|
|
<el-select v-model="sourceAdminClassIds" placeholder="请选择来源行政班" clearable multiple collapse-tags collapse-tags-tooltip class="form-control">
|
|
|
|
|
<el-option v-for="schoolClass in adminClassOptions" :key="schoolClass.schoolClassId" :label="schoolClass.className" :value="schoolClass.schoolClassId" />
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="班级名称" prop="className">
|
|
|
|
|
<el-input v-model="formInline.className" placeholder="请输入班级名称" class="form-control" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item v-if="activeClassType === 'TEACHING' && isCreate" label="名单创建">
|
|
|
|
|
<div class="import-create-actions">
|
|
|
|
|
<el-upload
|
|
|
|
|
action="/api/user/uploadTeachingClassByStudentMembers"
|
|
|
|
|
:data="importCreateUploadData"
|
|
|
|
|
:show-file-list="false"
|
|
|
|
|
:before-upload="beforeImportCreate"
|
|
|
|
|
:http-request="uploadImportCreate"
|
|
|
|
|
>
|
|
|
|
|
<el-button type="primary">导入名单创建</el-button>
|
|
|
|
|
</el-upload>
|
|
|
|
|
<el-button @click="downloadTeachingClassImportTemplate">模板下载</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-form>
|
|
|
|
|
<template #footer>
|
|
|
|
|
<span class="dialog-footer">
|
|
|
|
|
@ -89,6 +113,51 @@
|
|
|
|
|
</span>
|
|
|
|
|
</template>
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
|
|
|
|
<el-dialog v-model="memberDialogVisible" width="900px" :title="memberDialogTitle" class="teacher-dialog" @close="closeMemberDialog">
|
|
|
|
|
<div class="member-toolbar">
|
|
|
|
|
<el-input v-model="memberParams.name" placeholder="学生姓名" clearable class="member-query" />
|
|
|
|
|
<el-input v-model="memberParams.userName" placeholder="学号" clearable class="member-query" />
|
|
|
|
|
<el-button type="primary" :icon="Search" @click="getMemberList">查询</el-button>
|
|
|
|
|
<el-button :icon="Refresh" @click="refreshMemberList">刷新</el-button>
|
|
|
|
|
<template v-if="selectedTeachingClassCanOperate">
|
|
|
|
|
<el-upload
|
|
|
|
|
action="/api/user/uploadTeachingClassStudentMembers"
|
|
|
|
|
:data="memberUploadData"
|
|
|
|
|
:show-file-list="false"
|
|
|
|
|
:before-upload="beforeImportMembers"
|
|
|
|
|
:http-request="uploadMemberImport"
|
|
|
|
|
>
|
|
|
|
|
<el-button type="primary">导入成员</el-button>
|
|
|
|
|
</el-upload>
|
|
|
|
|
<el-button @click="downloadMemberImportTemplate">模板下载</el-button>
|
|
|
|
|
</template>
|
|
|
|
|
</div>
|
|
|
|
|
<el-table :data="memberList" border v-loading="memberLoading" class="member-table" :header-cell-style="tableHeaderStyle">
|
|
|
|
|
<el-table-column label="学生姓名" prop="name" align="center" />
|
|
|
|
|
<el-table-column label="学号" prop="username" align="center" />
|
|
|
|
|
<el-table-column label="所属班级" prop="className" align="center" />
|
|
|
|
|
<el-table-column label="电话" prop="phone" align="center" />
|
|
|
|
|
<el-table-column label="邮箱" prop="email" align="center" />
|
|
|
|
|
<el-table-column label="账号来源" prop="codeFrom" align="center" />
|
|
|
|
|
<el-table-column label="操作" align="center" width="210">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<template v-if="selectedTeachingClassCanOperate">
|
|
|
|
|
<el-button type="danger" text @click="removeMember(row)">移除</el-button>
|
|
|
|
|
<el-button type="primary" text @click="initializeMember(row)">数据初始化</el-button>
|
|
|
|
|
</template>
|
|
|
|
|
<el-tag v-else type="info">仅查看</el-tag>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
</el-table>
|
|
|
|
|
<pagination
|
|
|
|
|
v-show="memberTotal > 0"
|
|
|
|
|
:total="memberTotal"
|
|
|
|
|
v-model:page="memberParams.index"
|
|
|
|
|
v-model:limit="memberParams.size"
|
|
|
|
|
@pagination="getMemberList"
|
|
|
|
|
/>
|
|
|
|
|
</el-dialog>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
@ -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();
|
|
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
@ -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%;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
|