班级管理/学员

dev
LiuJiaNan 2024-03-19 16:17:08 +08:00
parent 54916b2756
commit 6699355494
9 changed files with 4320 additions and 1763 deletions

4986
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -46,7 +46,8 @@
"vue3-pdfjs": "^0.1.6",
"vue3-print-nb": "^0.1.4",
"vue3-puzzle-vcode": "^1.1.5",
"vue3-seamless-scroll": "^2.0.1"
"vue3-seamless-scroll": "^2.0.1",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@our-patches/postcss-px-to-viewport": "^1.2.0",

View File

@ -30,3 +30,13 @@ export const setClassManagementAdd = (params) => post("/class/add", params); //
export const setClassManagementEdit = (params) => post("/class/edit", params); // 班级管理修改
export const setClassManagementModifyExamTimes = (params) =>
post("/class/editNumberofexams", params); // 班级管理修改考试次数
export const getClassManagementStudentList = (params) =>
post("/student/classStudentList", params); // 班级管理学员列表
export const getClassManagementExportLearningRecords = (params) =>
post("/student/exportStudentList", params); // 班级管理导出学员学习记录
export const setClassManagementStudentDelete = (params) =>
post("/student/deleteStudent", params); // 班级管理学员删除
export const getClassManagementSelectStudentList = (params) =>
post("/user/studentList", params); // 班级管理新增学员列表
export const getClassManagementSelectStudentAdd = (params) =>
post("/student/add", params); // 班级管理新增学员保存

View File

@ -4,9 +4,9 @@
<el-tab-pane
label="基本信息"
:name="
!CLASS_ID
type === 'add'
? '/training_process_management/class_management/add'
: STATE === '1'
: type === 'edit'
? '/training_process_management/class_management/edit'
: '/training_process_management/class_management/view'
"
@ -44,7 +44,7 @@ import { useRoute, useRouter } from "vue-router";
const router = useRouter();
const route = useRoute();
const { CLASS_ID, STATE } = route.query;
const { CLASS_ID, type } = route.query;
const active = ref(route.path);
const fnTabChange = (path) => {
router.replace({

View File

@ -0,0 +1,170 @@
<template>
<el-dialog v-model="visible" title="添加学员">
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
<el-row>
<el-col :span="24">
<el-form-item label="发布类型" prop="releaseType">
<el-radio-group v-model="form.releaseType">
<el-radio value="1">按身份发布</el-radio>
<el-radio value="2">按人员发布</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="从业身份" prop="personnelTypes">
<el-checkbox-group
v-model="form.personnelTypes"
@change="fnGetDataTransfer"
>
<el-checkbox
v-for="item in personnelTypeList"
:key="item.DICTIONARIES_ID"
:label="item.NAME"
:value="item.DICTIONARIES_ID"
/>
</el-checkbox-group>
</el-form-item>
</el-col>
</el-row>
</el-form>
<layout-table
ref="tableRef"
:data="list"
:show-pagination="false"
max-height="500"
row-key="USER_ID"
>
<el-table-column
v-if="form.releaseType === '2'"
reserve-selection
type="selection"
width="55"
/>
<el-table-column label="序号" width="60" type="index" />
<el-table-column prop="NAME" label="姓名" />
<el-table-column prop="DEPARTMENT_NAME" label="部门" />
<el-table-column prop="POST_NAME" label="岗位" />
<el-table-column prop="USER_ID_CARD" label="身份证号" />
<el-table-column prop="PERSONNEL_TYPE_NAME" label="从业身份" />
</layout-table>
<template #footer>
<el-button @click="fnClose"></el-button>
<el-button type="primary" @click="fnSubmit"></el-button>
</template>
</el-dialog>
</template>
<script setup>
import { useVModels } from "@vueuse/core";
import useListData from "@/assets/js/useListData.js";
import {
getClassManagementSelectStudentAdd,
getClassManagementSelectStudentList,
} from "@/request/training_process_management.js";
import { layoutFnGetPersonnelType } from "@/assets/js/data_dictionary.js";
import { ref, watch } from "vue";
import { debounce } from "throttle-debounce";
import useFormValidate from "@/assets/js/useFormValidate.js";
import { ElMessage } from "element-plus";
import { useRoute } from "vue-router";
const route = useRoute();
const { CLASS_ID, EXAMINATION } = route.query;
const props = defineProps({
visible: {
type: Boolean,
required: true,
default: false,
},
form: {
type: Object,
required: true,
default: () => ({}),
},
selectList: {
type: Array,
required: true,
default: () => [],
},
});
const emits = defineEmits(["update:visible", "update:form", "get-data"]);
const { visible, form } = useVModels(props, emits);
const { list, fnGetData, tableRef } = useListData(
getClassManagementSelectStudentList,
{
usePagination: false,
key: "userList",
immediate: false,
callbackFn: (list) => {
for (let i = 0; i < props.selectList.length; i++) {
for (let j = 0; j < list.length; j++) {
if (list[j].USER_ID === props.selectList[i].USER_ID) {
list.splice(j, 1);
break;
}
}
}
},
}
);
const rules = {
releaseType: [{ required: true, message: "请选择发布类型", trigger: "blur" }],
personnelTypes: [
{ required: true, message: "请选择从业身份", trigger: "blur" },
],
};
const formRef = ref(null);
const fnGetDataTransfer = () => {
fnGetData({
PERSONNEL_TYPES: form.value.personnelTypes.join(","),
});
};
watch(
() => visible.value,
() => {
if (visible.value) {
if (form.value.personnelTypes.length > 0) fnGetDataTransfer();
}
}
);
const personnelTypeList = await layoutFnGetPersonnelType();
const fnClose = () => {
formRef.value.resetFields();
tableRef.value.clearSelection();
visible.value = false;
};
const fnSubmit = debounce(
1000,
async () => {
await useFormValidate(formRef);
if (list.value.length === 0) {
ElMessage.warning("当前从业身份没有学习人员,请重新选择");
return;
}
let currentIds = "";
if (form.value.releaseType === "1") {
currentIds = list.value.map((item) => item.USER_ID).join(",");
} else {
const selectionData = tableRef.value.getSelectionRows();
if (selectionData.length === 0) {
ElMessage.warning("请选择学员");
return;
}
currentIds = selectionData.map((item) => item.USER_ID).join(",");
}
await getClassManagementSelectStudentAdd({
RELEASE_TYPE: form.value.releaseType,
PERSONNEL_TYPES: form.value.personnelTypes.join(","),
CLASS_ID,
EXAMINATION,
userIds: currentIds,
});
ElMessage.success("添加成功");
fnClose();
emits("get-data");
},
{ atBegin: true }
);
</script>
<style scoped lang="scss"></style>

View File

@ -9,7 +9,7 @@
<el-input
v-model="data.form.NAME"
placeholder="请输入班级名称"
:disabled="isDisabled"
:disabled="isEdit"
/>
</el-form-item>
</el-col>
@ -23,7 +23,7 @@
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
:disabled="isDisabled"
:disabled="isEdit"
/>
</el-form-item>
</el-col>
@ -33,7 +33,7 @@
ref="trainingTypeRef"
v-model="data.form.TRAINTYPE"
type="trainingType"
:disabled="isDisabled"
:disabled="isEdit"
@update:model-value="
data.form.POSTTYPE = '';
data.form.TRAINLEVEL = '';
@ -48,7 +48,7 @@
v-model="data.form.POSTTYPE"
type="postType"
:search-value="data.form.TRAINTYPE"
:disabled="isDisabled"
:disabled="isEdit"
@update:model-value="data.form.TRAINLEVEL = ''"
/>
</el-form-item>
@ -70,7 +70,7 @@
v-model="data.form.TRAINLEVEL"
type="trainingLevel"
:search-value="data.form.POSTTYPE"
:disabled="isDisabled"
:disabled="isEdit"
@throw-data="data.trainingLevelList = $event"
/>
</el-form-item>
@ -86,7 +86,7 @@
<el-select
v-model="data.form.RECORDOR"
filterable
:disabled="isDisabled"
:disabled="isEdit"
>
<el-option
v-for="item in data.recordingPersonnelList"
@ -102,7 +102,7 @@
<el-select
v-model="data.form.ASSESSOR"
filterable
:disabled="isDisabled"
:disabled="isEdit"
>
<el-option
v-for="item in data.assessorsList"
@ -118,7 +118,7 @@
<el-select
v-model="data.form.SAFETYDEPTOR"
filterable
:disabled="isDisabled"
:disabled="isEdit"
>
<el-option
v-for="item in data.headOfSafetyManagementDepartmentList"
@ -134,7 +134,7 @@
<el-input
v-model="data.form.PRINCIPAL"
placeholder="请输入负责人"
:disabled="isDisabled"
:disabled="isEdit"
/>
</el-form-item>
</el-col>
@ -143,7 +143,7 @@
<el-input
v-model="data.form.PRINCIPAL_PHONE"
placeholder="请输入负责人电话"
:disabled="isDisabled"
:disabled="isEdit"
/>
</el-form-item>
</el-col>
@ -153,10 +153,7 @@
</el-col>
<el-col :span="12">
<el-form-item label="是否开启考试" prop="EXAMINATION">
<el-radio-group
v-model="data.form.EXAMINATION"
:disabled="isDisabled"
>
<el-radio-group v-model="data.form.EXAMINATION" :disabled="isEdit">
<el-radio :label="1"></el-radio>
<el-radio :label="0"></el-radio>
</el-radio-group>
@ -172,7 +169,7 @@
v-model="data.form.NUMBEROFEXAMS"
:min="1"
:max="2147483600"
:disabled="isDisabled"
:disabled="isEdit"
/>
<el-button
v-if="STATE && STATE !== '1' && STATE !== '6'"
@ -187,7 +184,7 @@
</el-col>
<el-col :span="12">
<el-form-item label="是否人脸识别" prop="ISFACE">
<el-radio-group v-model="data.form.ISFACE" :disabled="isDisabled">
<el-radio-group v-model="data.form.ISFACE" :disabled="isEdit">
<el-radio label="1"></el-radio>
<el-radio label="0"></el-radio>
</el-radio-group>
@ -195,10 +192,7 @@
</el-col>
<el-col v-if="data.form.EXAMINATION === 1" :span="12">
<el-form-item label="是否效果评估" prop="ISSTRENGTHEN">
<el-radio-group
v-model="data.form.ISSTRENGTHEN"
:disabled="isDisabled"
>
<el-radio-group v-model="data.form.ISSTRENGTHEN" :disabled="isEdit">
<el-radio label="2">强制</el-radio>
<el-radio label="1"></el-radio>
<el-radio label="0"></el-radio>
@ -207,10 +201,8 @@
</el-col>
</el-row>
</el-form>
<div class="mt-10 tc">
<el-button type="primary" @click="fnSubmit">
{{ !CLASS_ID ? "保存并下一步" : "保存" }}
</el-button>
<div v-if="!isEdit" class="mt-10 tc">
<el-button type="primary" @click="fnSubmit"> </el-button>
</div>
<modify-exam-times
:id="CLASS_ID"
@ -237,8 +229,8 @@ import ModifyExamTimes from "./modify_exam_times.vue";
const route = useRoute();
const router = useRouter();
const { STATE, CLASS_ID } = route.query;
const isDisabled = STATE && STATE !== "1";
const { STATE, CLASS_ID, type } = route.query;
const isEdit = STATE && STATE !== "1";
const formRef = ref(null);
const trainingTypeRef = ref(null);
const postTypeRef = ref(null);
@ -351,13 +343,14 @@ const fnSubmit = debounce(
await router.replace({
path: !CLASS_ID
? "/training_process_management/class_management/student"
: STATE === "1"
? "/training_process_management/class_management/edit"
: "/training_process_management/class_management/view",
: type === "add"
? "/training_process_management/class_management/add"
: "/training_process_management/class_management/edit",
query: {
...route.query,
CLASS_ID: !CLASS_ID ? resData.CLASS_ID : CLASS_ID,
TRAINTYPE: data.form.TRAINTYPE,
EXAMINATION: data.form.EXAMINATION,
},
});
},

View File

@ -1,7 +1,28 @@
<template>
<div></div>
<div>
<div v-if="!isEdit" class="mt-10 tc">
<el-button
@click="
router.replace({
path: '/training_process_management/class_management/student',
query: { ...route.query },
})
"
>
上一步
</el-button>
<el-button type="primary"> 完成 </el-button>
</div>
</div>
</template>
<script setup></script>
<script setup>
import { useRoute, useRouter } from "vue-router";
const route = useRoute();
const router = useRouter();
const { STATE } = route.query;
const isEdit = STATE && STATE !== "1";
</script>
<style scoped lang="scss"></style>

View File

@ -1,7 +1,348 @@
<template>
<div></div>
<div>
<el-form
:model="searchForm"
label-width="100px"
@submit.prevent="fnGetDataTransfer"
>
<el-row>
<el-col :span="6">
<el-form-item label="姓名" prop="NAME">
<el-input v-model="searchForm.NAME" placeholder="请输入姓名" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="部门" prop="DEPARTMENT_ID">
<layout-department
v-model="searchForm.DEPARTMENT_ID"
@update:model-value="fnGetPost"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="岗位" prop="POST_ID">
<el-select v-model="searchForm.POST_ID">
<el-option
v-for="item in data.postList"
:key="item.POST_ID"
:label="item.NAME"
:value="item.POST_ID"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="学习状态" prop="STUDYSTATE">
<el-select v-model="searchForm.STUDYSTATE">
<el-option
v-for="item in learningStatus"
:key="item.ID"
:label="item.NAME"
:value="item.ID"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="考试状态" prop="STAGEEXAMSTATE">
<el-select v-model="searchForm.STAGEEXAMSTATE">
<el-option
v-for="item in examStatus"
:key="item.ID"
:label="item.NAME"
:value="item.ID"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="入班时间" prop="TIME">
<el-date-picker
v-model="searchForm.TIME"
value-format="YYYY-MM-DD"
format="YYYY-MM-DD"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label-width="10px">
<el-button type="primary" native-type="submit">搜索</el-button>
<el-button native-type="reset" @click="fnGetDataTransfer">
重置
</el-button>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label-width="10px" class="end">
<el-button
v-if="!isEdit"
type="primary"
@click="data.addStudentDialog.visible = true"
>
新增学员
</el-button>
<el-button
v-if="isEdit"
type="primary"
@click="fnGetLearningRecords"
>
导出学员学习记录
</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
<layout-table :data="list" :show-pagination="false" max-height="500">
<el-table-column label="序号" width="60" type="index" fixed="left" />
<el-table-column fixed width="100" prop="NAME" label="姓名" />
<el-table-column width="200" prop="DEPARTMENT_NAME" label="部门" />
<el-table-column width="200" prop="POST_NAME" label="岗位" />
<el-table-column width="200" prop="USER_ID_CARD" label="身份证号" />
<el-table-column width="200" prop="FILE_NUMBER" label="档案编号" />
<el-table-column width="100" label="人脸认证">
<template #default="{ row }">
{{ row.AUTHENTICATION === "0" ? "未认证" : "已认证" }}
</template>
</el-table-column>
<el-table-column width="100" prop="ALL_CLASSHOUR" label="要求学时" />
<el-table-column width="100" label="已完成学时">
<template #default="{ row }">
<template v-if="row.COMPLETE_CLASSHOUR">
{{
row.COMPLETE_CLASSHOUR === "0.0"
? 0
: parseFloat(row.COMPLETE_CLASSHOUR).toFixed(1)
}}
</template>
</template>
</el-table-column>
<el-table-column width="100" label="考试状态">
<template #default="{ row }">
{{ translationStatus(row.STAGEEXAMSTATE, examStatus) }}
</template>
</el-table-column>
<el-table-column width="100" label="学习状态">
<template #default="{ row }">
{{ translationStatus(row.STUDYSTATE, learningStatus) }}
</template>
</el-table-column>
<el-table-column prop="OPERATORNAME" label="操作人" />
<el-table-column width="150" prop="CREATTIME" label="入班时间" />
<el-table-column label="操作" width="150" fixed="right">
<template #default="{ row }">
<el-button
v-if="!isEdit"
type="primary"
text
link
@click="fnDelete(row.STUDENT_ID)"
>
从本班移除
</el-button>
<el-button v-if="isEdit" type="primary" text link>
学习记录
<!-- TODO -->
</el-button>
</template>
</el-table-column>
</layout-table>
<div v-if="!isEdit" class="mt-10 tc">
<el-button
@click="
router.replace({
path:
type === 'add'
? '/training_process_management/class_management/add'
: '/training_process_management/class_management/edit',
query: { ...route.query },
})
"
>
上一步
</el-button>
<el-button
type="primary"
@click="
router.replace({
path: '/training_process_management/class_management/curriculum',
query: { ...route.query },
})
"
>
下一步
</el-button>
</div>
<add-student
v-model:visible="data.addStudentDialog.visible"
v-model:form="data.addStudentDialog.form"
:select-list="list"
@get-data="fnGetDataTransfer"
/>
</div>
</template>
<script setup></script>
<script setup>
import LayoutDepartment from "@/components/department/index.vue";
import { getPostListAll } from "@/request/data_dictionary.js";
import { reactive } from "vue";
import { translationStatus } from "@/assets/js/utils.js";
import useListData from "@/assets/js/useListData.js";
import {
getClassManagementExportLearningRecords,
getClassManagementStudentList,
setClassManagementStudentDelete,
} from "@/request/training_process_management.js";
import { useRoute, useRouter } from "vue-router";
import { ElMessage, ElMessageBox } from "element-plus";
import * as XLSX from "xlsx";
import { debounce } from "throttle-debounce";
import AddStudent from "./add_student.vue";
const route = useRoute();
const router = useRouter();
const { CLASS_ID, STATE, type } = route.query;
const isEdit = STATE && STATE !== "1";
const learningStatus = [
{ ID: "0", NAME: "未学习" },
{ ID: "1", NAME: "学习中" },
{ ID: "2", NAME: "已学完" },
{ ID: "3", NAME: "已完成" },
{ ID: "4", NAME: "未完成" },
{ ID: "5", NAME: "待评估" },
{ ID: "6", NAME: "评估未合格" },
];
const examStatus = [
{ ID: "0", NAME: "不考试" },
{ ID: "1", NAME: "待考试" },
{ ID: "2", NAME: "考试未通过" },
{ ID: "3", NAME: "考试通过" },
{ ID: "4", NAME: "未参加" },
];
const data = reactive({
postList: [],
addStudentDialog: {
visible: false,
form: {
releaseType: "",
personnelTypes: [],
},
},
});
const { list, searchForm, fnGetData } = useListData(
getClassManagementStudentList,
{
otherParams: { CLASS_ID },
usePagination: false,
callbackFn: (list, resData) => {
data.addStudentDialog.form.releaseType =
resData.classInfo.RELEASE_TYPE || "";
data.addStudentDialog.form.personnelTypes = resData.classInfo
.PERSONNEL_TYPES
? resData.classInfo.PERSONNEL_TYPES.split(",")
: [];
},
}
);
const fnGetDataTransfer = () => {
fnGetData({
START_TIME: searchForm.value.TIME?.[0],
END_TIME: searchForm.value.TIME?.[1],
});
};
const fnGetPost = async (DEPARTMENT_ID) => {
data.postList = [];
searchForm.value.POST_ID = "";
if (!DEPARTMENT_ID) return;
const resData = await getPostListAll({ DEPARTMENT_ID });
data.postList = resData.postList;
};
const fnGetLearningRecords = async () => {
await ElMessageBox.confirm("确定要导出查询出来所有的学习记录?", {
type: "warning",
});
const resData = await getClassManagementExportLearningRecords({
CLASS_ID,
...searchForm.value,
START_TIME: searchForm.value.TIME?.[0],
END_TIME: searchForm.value.TIME?.[1],
});
if (resData.varList.length > 0) fnExportLearningRecords(resData.varList);
else ElMessage.warning("没有学习记录");
};
const fnExportLearningRecords = (list) => {
const tableData = [
[
"序号",
"身份证",
"姓名",
"性别",
"手机号",
"部门",
"岗位",
"要求总学时",
"已完成学时",
"是否考试通过",
"考试分数",
"学习状态",
"班级名称",
],
];
list.forEach((item, index) => {
for (let i = 0; i < learningStatus.length; i++) {
if (learningStatus[i].ID === item.STUDYSTATE) {
item.STUDYSTATE = learningStatus[i].NAME;
break;
}
}
for (let i = 0; i < examStatus.length; i++) {
if (learningStatus[i].ID === item.STAGEEXAMSTATE) {
item.STAGEEXAMSTATE = learningStatus[i].NAME;
break;
}
}
if (item.ALL_CLASSHOUR === "0.0") item.ALL_CLASSHOUR = "0";
const COMPLETE_CLASSHOUR =
item.COMPLETE_CLASSHOUR === "0.0"
? 0
: parseFloat(item.COMPLETE_CLASSHOUR).toFixed(1);
tableData.push([
index + 1,
item.USER_ID_CARD,
item.NAME,
item.SEX,
item.PHONE,
item.DEPARTMENT_NAME,
item.POST_NAME,
item.ALL_CLASSHOUR,
COMPLETE_CLASSHOUR,
item.STAGEEXAMSTATE,
item.EXAMSCORE,
item.STUDYSTATE,
item.CLASS_NAME,
]);
});
const ws = XLSX.utils.aoa_to_sheet(tableData);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, "学员统计");
XLSX.writeFile(wb, "学员统计表.xlsx");
ElMessage.success("导出成功");
};
const fnDelete = debounce(
1000,
async (STUDENT_ID) => {
await ElMessageBox.confirm("确定要从本班移除该学员吗?", {
type: "warning",
});
await setClassManagementStudentDelete({ STUDENT_ID });
ElMessage.success("移除成功");
fnGetDataTransfer();
},
{ atBegin: type }
);
</script>
<style scoped lang="scss"></style>

View File

@ -174,6 +174,8 @@
STATE: row.STATE,
CLASS_ID: row.CLASS_ID,
TRAINTYPE: row.TRAINTYPE,
EXAMINATION: row.EXAMINATION,
type: row.STATE === '1' ? 'edit' : 'view',
},
})
"
@ -191,6 +193,8 @@
STATE: row.STATE,
CLASS_ID: row.CLASS_ID,
TRAINTYPE: row.TRAINTYPE,
EXAMINATION: row.EXAMINATION,
type: 'edit',
},
})
"
@ -208,6 +212,8 @@
STATE: row.STATE,
CLASS_ID: row.CLASS_ID,
TRAINTYPE: row.TRAINTYPE,
EXAMINATION: row.EXAMINATION,
type: 'edit',
},
})
"
@ -240,6 +246,7 @@
@click="
router.push({
path: '/training_process_management/class_management/add',
query: { type: 'add' },
})
"
>