From c293e8f2c2c8f1eae95b0255a8745456963511d2 Mon Sep 17 00:00:00 2001 From: zhouwx <1175765986@qq.com> Date: 星期五, 05 七月 2024 15:11:44 +0800 Subject: [PATCH] 线下登记、统计 --- src/api/onlineEducation/examRecord.js | 35 ++ src/utils/directivesNew.js | 48 +++ src/main.js | 3 src/views/onlineEducation/offlineEducation/components/recordDialog.vue | 333 +++++++++++++++++++++++++ src/utils/directive.ts | 4 src/api/onlineEducation/count.js | 9 src/views/onlineEducation/count/index.vue | 176 +++++++++++++ src/views/onlineEducation/offlineEducation/index.vue | 133 +++++++++ 8 files changed, 725 insertions(+), 16 deletions(-) diff --git a/src/api/onlineEducation/count.js b/src/api/onlineEducation/count.js new file mode 100644 index 0000000..d9316ea --- /dev/null +++ b/src/api/onlineEducation/count.js @@ -0,0 +1,9 @@ +import request from '@/utils/request' + +export function getCompanyCount(params) { + return request({ + url: '/statistic/companyStatistic', + method: 'get', + params: params + }) +} diff --git a/src/api/onlineEducation/examRecord.js b/src/api/onlineEducation/examRecord.js new file mode 100644 index 0000000..4aa871c --- /dev/null +++ b/src/api/onlineEducation/examRecord.js @@ -0,0 +1,35 @@ +import request from '@/utils/request' + +export function getRecord(param) { + return request({ + url: '/exam-record/list', + method: 'get', + params: param + }) +} + +export function addRecord(data) { + return request({ + url: '/exam-record', + method: 'post', + data: data + }) +} + + +export function editRecord(params) { + return request({ + url: `/exam-record`, + method: 'put', + data: params + }) +} + + +export function delRecord(userId) { + return request({ + url: '/exam-record/' + userId, + method: 'delete' + }) +} + diff --git a/src/main.js b/src/main.js index ad736f7..d0d0667 100644 --- a/src/main.js +++ b/src/main.js @@ -51,6 +51,7 @@ import { Boot } from '@wangeditor/editor' import attachmentModule from '@wangeditor/plugin-upload-attachment' import loadMore from '@/utils/selectLoadMoreDirective' +import loadMoreNew from '@/utils/directive' import "video.js/dist/video-js.css" Boot.registerModule(attachmentModule) const app = createApp(App) @@ -83,6 +84,8 @@ app.use(preReClick) app.component('svg-icon', SvgIcon) app.directive('loadMore',loadMore) +app.directive('loadMoreNew',loadMoreNew) +// app.use(Directives) directive(app) // 使用element-plus 并且设置全局的大小 diff --git a/src/utils/directive.ts b/src/utils/directive.ts index c3ceb1a..166d1cf 100644 --- a/src/utils/directive.ts +++ b/src/utils/directive.ts @@ -1,6 +1,6 @@ import { Directive, DirectiveBinding } from 'vue' -const loadMore: Directive = { +const loadMoreNew: Directive = { beforeMount(el: any, binding: DirectiveBinding) { let arg = binding.arg as any if (!arg) return @@ -23,4 +23,4 @@ } } } -export default loadMore +export default loadMoreNew diff --git a/src/utils/directivesNew.js b/src/utils/directivesNew.js new file mode 100644 index 0000000..56f1e32 --- /dev/null +++ b/src/utils/directivesNew.js @@ -0,0 +1,48 @@ +// // import Vue from 'vue' +// // +// // export default () => { +// // Vue.directive('selectScroll', { +// // bind (el, binding) { +// // // 如上图,我通过v-if来控制了两个select框,当没有binding.arg这个参数时,我只能监听到企业类型下的select框,所以,我通过传参控制了监听的哪个select框 +// // var className = '.' + binding.arg +// // el.className = binding.arg +// // // 获取滚动页面DOM +// // const SCROLL_DOM = el.querySelector(`${className} .el-select-dropdown .el-select-dropdown__wrap`) +// // // const SCROLL_DOM = el.querySelector(“.el-select-dropdown .el-select-dropdown__wrap“) +// // SCROLL_DOM.addEventListener('scroll', function () { +// // // 当前的滚动位置 减去 上一次的滚动位置 +// // // 如果为true则代表向上滚动,false代表向下滚动 +// // const CONDITION = this.scrollHeight - this.scrollTop <= this.clientHeight +// // // 如果已达到指定位置则触发 +// // if (CONDITION) { +// // // 将滚动行为告诉组件 +// // binding.value() +// // } +// // }) +// // } +// // }) +// // } +// +// import { Directive, DirectiveBinding } from 'vue' +// +// const selectScroll: Directive = { +// beforeMount(el: any, binding: DirectiveBinding) { +// // 如上图,我通过v-if来控制了两个select框,当没有binding.arg这个参数时,我只能监听到企业类型下的select框,所以,我通过传参控制了监听的哪个select框 +// const className = '.' + binding.arg; +// el.className = binding.arg +// // 获取滚动页面DOM +// const SCROLL_DOM = el.querySelector(`${className} .el-select-dropdown .el-select-dropdown__wrap`) +// // const SCROLL_DOM = el.querySelector(“.el-select-dropdown .el-select-dropdown__wrap“) +// SCROLL_DOM.addEventListener('scroll', function () { +// // 当前的滚动位置 减去 上一次的滚动位置 +// // 如果为true则代表向上滚动,false代表向下滚动 +// const CONDITION = this.scrollHeight - this.scrollTop <= this.clientHeight +// // 如果已达到指定位置则触发 +// if (CONDITION) { +// // 将滚动行为告诉组件 +// binding.value() +// } +// }) +// }, +// } +// export default selectScroll diff --git a/src/views/onlineEducation/count/index.vue b/src/views/onlineEducation/count/index.vue index 4c0d406..3146e79 100644 --- a/src/views/onlineEducation/count/index.vue +++ b/src/views/onlineEducation/count/index.vue @@ -1,12 +1,174 @@ <template> -<div>数据统计</div> + <div class="app-container"> + <div style="margin-bottom: 10px"> + <el-form style="display: flex"> + <el-form-item label="企业:"> + <el-select + v-model="state.queryParams.companyId" + style="width: 100%" + v-loadMore="loadMore" + class="m-2" + placeholder="请选择所属企业" + popper-class="more_select_dropdown" + > + <el-option + v-for="item in state.companyList" + :key="item.id" + :label="item.name" + :value="item.id" + /> + </el-select> + </el-form-item> + <el-form-item label="时间范围:" style="margin-left: 20px"> + <el-date-picker + v-model="searchTime" + type="daterange" + @change="changeTime" + range-separator="至" + start-placeholder="开始日期" + end-placeholder="结束日期" + value-format="YYYY-MM-DD 00:00:00" + /> + </el-form-item> + <el-form-item style="margin-left: 50px"> + <el-radio-group v-model="state.queryParams.type"> + <el-radio :label="1">线上教育</el-radio> + <el-radio :label="2">线下教育</el-radio> + <el-radio :label="null">全部</el-radio> + </el-radio-group> + </el-form-item> + <el-form-item> + <el-button type="primary" style="margin-left: 30px" @click="searchClick">查询</el-button> + <el-button plain @click="reset">重置</el-button> + </el-form-item> + + </el-form> + </div> + <!-- 表格数据 --> + <el-table v-loading="loading" :data="state.dataList" :border="true" row-key="id"> + <el-table-column label="序号" type="index" align="center" width="80" /> + <el-table-column label="企业名称" prop="sort" align="center" /> + <el-table-column label="企业编号" prop="sort" align="center" width="80" /> + <el-table-column label="总批次/人数" prop="sort" align="center" width="80" /> + <el-table-column label="三级" prop="sort" align="center" width="80" /> + <el-table-column label="二级" prop="sort" align="center" width="80" /> + <el-table-column label="一级" prop="sort" align="center" width="80" /> + <el-table-column label="考试人次" prop="sort" align="center" width="80" /> + <el-table-column label="合格人次" prop="sort" align="center" width="80" /> + <el-table-column label="考试合格率" prop="sort" align="center" width="80" /> + </el-table> + <pagination + v-show="state.total > 0" + :total="state.total" + v-model:page="state.queryParams.pageNum" + v-model:limit="state.queryParams.pageSize" + @pagination="getList" + /> + + </div> </template> + <script setup> +import {getCurrentInstance, onMounted, reactive, ref, toRefs} from "vue"; +import {ElMessage, ElMessageBox} from "element-plus"; + +import {delClassification, getClassification} from "@/api/onlineEducation/courseClass"; +import {getCompany} from "@/api/onlineEducation/company"; +import {getCompanyCount} from "@/api/onlineEducation/count"; +const { proxy } = getCurrentInstance(); +const loading = ref(false); +const areaRef = ref(); +const searchTime = ref([]); +const state = reactive({ + queryParams: { + companyId: '', + type: null, + endTime: '', + startTime: '', + pageNum: 1, + pageSize: 10, + }, + total: 0, + dataList: [ + ], + companyList: [], + pageNum: 1, + pageSize: 10, +}); + +//页面加载 +onMounted(() => { + getCompanyList(); + getList(); + +}); +const getList = async () => { + loading.value = true; + const res = await getCompanyCount(state.queryParams); + if(res.code === 200){ + state.dataList = res.data + }else{ + ElMessage.warning(res.message) + } + loading.value = false; +} + +const finshed = ref(false) +const getCompanyList = async (type)=>{ + if (type === 'open' && state.pageNum !== 1) { + } else { + const queryParams = { + pageNum: state.pageNum, + pageSize: state.pageSize, + } + const res = await getCompany(queryParams) + if (res.code == 200) { + if (res.data.pageNum === state.pageNum) { + finshed.value = false; + if (state.pageNum == 1) { + state.companyList = res.data.list + } else { + state.companyList = state.companyList.concat(res.data.list) + } + } else { + finshed.value = true; + } + + } else { + ElMessage.warning(res.message) + } + console.log("state.companyList",state.companyList) + } +} +//触底函数 +const loadMore = () => { + console.log(' 触底了'); + // 防抖处理 + setTimeout(() => { + if (finshed.value) return //值为true,则代表没有数据了 + state.pageNum += 1 + getCompanyList('') + }, 500) +} +const changeTime=(value)=>{ + if(!value){ + state.queryParams.endTime = "" + state.queryParams.startTime = "" + } +} +const searchClick = () => { + if(searchTime.value && searchTime.value.length>0){ + state.queryParams.startTime = searchTime.value[0] + state.queryParams.endTime = searchTime.value[1] + } + getList(); +} +/** 重置新增的表单以及其他数据 */ +function reset() { + data.queryParams.name = ''; + data.queryParams.pageNum = 1; + getList(); +} + </script> - - - -<style scoped lang="scss"> - -</style> diff --git a/src/views/onlineEducation/offlineEducation/components/recordDialog.vue b/src/views/onlineEducation/offlineEducation/components/recordDialog.vue new file mode 100644 index 0000000..692fe97 --- /dev/null +++ b/src/views/onlineEducation/offlineEducation/components/recordDialog.vue @@ -0,0 +1,333 @@ +<template> + <div class="notice"> + <el-dialog + v-model="dialogVisible" + :title="state.title" + width="550px" + :before-close="handleClose" + > + <el-form :model="state.form" size="default" ref="superRef" :rules="state.formRules" label-width="180px" > + <el-form-item label="企业名称:" prop="companyName" > + <el-select + v-if="state.isAdmin" + v-model="state.form.companyName" + style="width: 100%" + v-loadMoreNew:[reselect]="handleScroll" + :popper-class="reselect.name" + @change="selectCompany" + class="item-width" + > + <el-option + v-for="item in state.companyList" + :key="item.id" + :label="item.name" + :value="item.name" + /> + </el-select> + + <el-input v-else v-model.trim="state.form.companyName" disabled ></el-input> + </el-form-item> + <el-form-item label="计划名称:" prop="planName" > + <el-input v-model.trim="state.form.planName" placeholder="请输入计划名称"></el-input> + </el-form-item> + <el-form-item label="学员:" prop="studentName" > + <el-select + v-model="state.form.studentName" + style="width: 100%" + v-loadMoreNew:[reselectStu]="handleScrollStu" + :popper-class="reselectStu.name" + class="m-2" + @change="selectValue" + placeholder="请选择学生" + popper-class="more_select_dropdown" + > + <el-option + v-for="item in state.studentList" + :key="item.id" + :label="item.name" + :value="item.name" + /> + </el-select> + </el-form-item> + <el-form-item label="课程名称:" prop="courseName" > + <el-input v-model.trim="state.form.courseName" :disabled="disabled" placeholder="请输入课程名称" ></el-input> + </el-form-item> + <el-form-item label="培训等级:" prop="level" > + <el-input v-model.trim="state.form.level" :disabled="disabled" placeholder="请输入培训等级" ></el-input> + </el-form-item> + <el-form-item label="要求课时(分):" prop="period" > + <el-input v-model.trim="state.form.period" :disabled="disabled" placeholder="请输入要求课时(分)" ></el-input> + </el-form-item> + <el-form-item label="实际课时(分):" prop="actualPeriod" > + <el-input v-model.trim="state.form.actualPeriod" :disabled="disabled" placeholder="请输入实际课时(分)" ></el-input> + </el-form-item> + <el-form-item label="考试成绩:" prop="score" > + <el-input v-model.trim="state.form.score" :disabled="disabled" placeholder="请输入考试成绩" ></el-input> + </el-form-item> + <el-form-item label="是否合格:" prop="passed" > + <el-radio-group v-model="state.form.passed" :disabled="disabled"> + <el-radio :label="0">不合格</el-radio> + <el-radio :label="1">合格</el-radio> + </el-radio-group> + </el-form-item> + </el-form> + <template #footer v-if="state.title !='查看'"> + <span class="dialog-footer"> + <el-button @click="handleClose" size="default">取 消</el-button> + <el-button type="primary" @click="onSubmit" size="default" v-preReClick>确认</el-button> + </span> + </template> + </el-dialog> + </div> +</template> +<script setup> +import {reactive, ref, toRefs, defineEmits, nextTick, onMounted} from 'vue' +import InfiniteList from 'vue3-infinite-list'; +import { View } from "@element-plus/icons-vue"; +import scorllSelect from '@/components/scrollSelect/index.vue' +import {ElMessage, ElMessageBox} from "element-plus"; +import {verifyPhone, verifyPwd, verifyUsername} from "@/utils/validate"; +import { checkUserName, checkPhone } from "@/api/login" +import {getStudent, resetPwd} from "@/api/onlineEducation/student" +import {Base64} from "js-base64" +import Cookies from "js-cookie"; +import {addStudent, checkStuIdNo, checkStuPhone, editStudent} from "@/api/onlineEducation/student"; +import {addRecord, editRecord} from "@/api/onlineEducation/examRecord"; +import {getCompany} from "@/api/onlineEducation/company"; + +const emit = defineEmits(["getList"]); +const dialogVisible = ref(false) +const superRef = ref(null) +const scrollRef = ref(null) +const reselect = reactive({ + name: 'company' +}) +const reselectStu = reactive({ + name: 'student' +}) + +const state = reactive({ + title: '', + form: { + id: null, + companyName: '', + planName: '', + studentId: '', + level: '', + period: '', + actualPeriod: '', + score: null, + passed: 0, + studentName: '', + courseName: '', + companyId: null + + }, + formRules:{ + companyName: [{ required: true, message: '请输入企业名称', trigger: 'blur' }], + planName: [{ required: true, message: '请输入计划名称', trigger: 'blur' }], + studentName: [{ required: true, message: '请选择学员', trigger: 'blur' }], + level: [{ required: true, message: '请输入培训等级', trigger: 'blur' }], + period: [{ required: true, message: '请输入要求课时(分)', trigger: 'blur' }], + actualPeriod: [{ required: true, message: '实际课时(分)', trigger: 'blur' }], + score: [{ required: true, message: '请输入考试成绩', trigger: 'blur' }], + }, + isAdmin: false, + studentList: [], + stuPageNum: 1, // 当前页码 + stuPageSize: 10, // 每页显示的数量 + hasMoreStu: null, // 是否还有更多选项 + pageNum: 1, + pageSize: 10, + companyList: [], + companyPageNum: 1, // 当前页码 + companyPageSize: 10, // 每页显示的数量 + hasMoreItems: null, // 是否还有更多选项 + +}) +onMounted(() => { + +}); + +const disabled = ref(false); +const openDialog = async (type, value) => { + await loadMoreStuData(); + if(state.isAdmin){ + await loadMoreCompanyData(); + } + const userInfo = JSON.parse(Cookies.get('userInfo')) + console.log("userInfo",userInfo) + if(userInfo.userType === 0){ + state.isAdmin = true; + state.form.userType = 0; + }else { + state.isAdmin = false; + state.form.companyId = userInfo.companyId; + state.form.companyName = userInfo.companyName; + state.form.userType = 1; + } + + state.title = type === 'add' ? '新增' : type ==='edit' ? '编辑' : type ==='pwd' ? '修改密码' : '查看' ; + if(type === 'edit' || type === 'view') { + if( type === 'view'){ + disabled.value = true; + } + state.form = value + // state.form.studentName = value.company.name; + console.log("ba",state.form) + } + if(type == 'pwd'){ + state.form.id = value.id + } + dialogVisible.value = true +} +const onSubmit = async () => { + const valid = await superRef.value.validate(); + if(valid){ + if(state.title == '新增'){ + const {id,...data} = state.form + const res = await addRecord(data) + if(res.code == 200){ + ElMessage.success(res.message) + emit('getList') + handleClose() + dialogVisible.value = false; + }else{ + ElMessage.warning(res.message) + } + }else if(state.title == '编辑'){ + const {id, name, phone, sex, companyId, empno, post, duty, idNo} = state.form + const data = {id, name, phone, sex, companyId, empno, post, duty, idNo} + const res = await editRecord(data) + if(res.code == 200){ + ElMessage.success(res.message) + emit('getList') + handleClose() + }else{ + ElMessage.warning(res.message) + } + }else{ + const {id,password} = state.form + const data = {id,password} + data.password = Base64.encode(data.password) + const res = await resetPwd(data) + if(res.code == 200){ + ElMessage.success(res.message) + emit('getList') + handleClose() + }else{ + ElMessage.warning(res.message) + } + } + } +} + +const handleClose = () => { + state.form = { + id: null, + companyName: '', + planName: '', + studentId: '', + level: '', + period: '', + actualPeriod: '', + score: null, + passed: 0, + studentName: '', + courseName: '', + companyId: null + + } + state.companyPageNum = 1; + state.companyPageSize = 10; + state.companyList = []; + state.stuPageNum = 1; + state.stuPageSize = 10; + state.studentList = []; + superRef.value.clearValidate(); + superRef.value.resetFields() + dialogVisible.value = false; +} + +const selectValue = (val) => { + state.studentList.forEach(item => { + if(item.name === val){ + state.form.studentId = item.id + } + }) +} + +const selectCompany = (val) => { + state.companyList.forEach(item => { + if(item.name === val){ + state.form.companyId = item.id + } + }) +} + + + +const handleScrollStu = () => { + console.log(' student',state.hasMoreStu); + if(state.stuPageNum >= state.hasMoreStu) return + state.stuPageNum++; + loadMoreStuData() + +} +const loadMoreStuData = async () => { + const queryParams = { + pageNum: state.stuPageNum, + pageSize: state.stuPageSize, + } + const res = await getStudent(queryParams) + if (res.code == 200) { + state.hasMoreStu = res.data.totalPage + const data = res.data + state.studentList = state.studentList.concat(data.list) + }else{ + ElMessage.warning(res.message) + } +} + +const handleScroll = () => { + console.log(' Company',state.hasMoreItems); + if(state.companyPageNum >= state.hasMoreItems) return + state.companyPageNum++; + loadMoreCompanyData() + +} +const loadMoreCompanyData = async () => { + const queryParams = { + pageNum: state.companyPageNum, + pageSize: state.companyPageSize, + } + const res = await getCompany(queryParams) + if (res.code == 200) { + state.hasMoreItems = res.data.totalPage + const data = res.data + state.companyList = state.companyList.concat(data.list) + }else{ + ElMessage.warning(res.message) + } +} + + + +defineExpose({ + openDialog +}); + +</script> + +<style scoped lang="scss"> +.notice{ + :deep(.el-form .el-form-item__label) { + font-size: 15px; + } + .file { + display: flex; + flex-direction: column; + align-items: flex-start; + } +} +</style> diff --git a/src/views/onlineEducation/offlineEducation/index.vue b/src/views/onlineEducation/offlineEducation/index.vue index a654d8b..75731b5 100644 --- a/src/views/onlineEducation/offlineEducation/index.vue +++ b/src/views/onlineEducation/offlineEducation/index.vue @@ -1,12 +1,131 @@ <template> -<div>线下教育登记</div> + <div class="app-container"> + <div style="margin-bottom: 10px"> + <el-button + type="primary" + @click="openDialog('add',{})" + >新增登记</el-button> + <el-button + type="primary" + plain + >批量导入</el-button> + </div> + <!-- 表格数据 --> + <el-table v-loading="loading" :data="dataList" :border="true"> + <el-table-column label="序号" type="index" align="center" width="80" /> + <el-table-column label="企业名称" prop="companyName" align="center" /> + <el-table-column label="计划名称" prop="planName" align="center" /> + <el-table-column label="学员姓名" prop="studentName" align="center" /> + <el-table-column label="性别" prop="sex" align="center" > + <template #default="scope"> + <span>{{scope.row.sex == 0 ? '男':'女'}}</span> + </template> + </el-table-column> + <el-table-column label="身份证号" prop="idNo" align="center" width="200" :show-overflow-tooltip="true"/> + <el-table-column label="课程名称" prop="courseName" align="center"/> + <el-table-column label="培训等级" prop="level" align="center"/> + <el-table-column label="要求课时(分)" prop="period" align="center"/> + <el-table-column label="实际课时(分)" prop="actualPeriod" align="center"/> + <el-table-column label="考试成绩" prop="score" align="center"/> + <el-table-column label="是否合格" prop="passed" align="center"> + <template #default="scope"> + <span>{{scope.row.passed == 0 ? '不合格':'合格'}}</span> + </template> + </el-table-column> + </el-table> + + <pagination + v-show="total > 0" + :total="total" + v-model:page="queryParams.pageNum" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + + <record-dialog ref="dialogRef" @getList=getList></record-dialog> + </div> </template> + <script setup> +import {getCurrentInstance, onMounted, onUnmounted, reactive, ref, toRefs} from "vue"; +import {ElMessage, ElMessageBox} from "element-plus"; +import {delCompany, getCompany} from "@/api/onlineEducation/company"; +import recordDialog from "./components/recordDialog.vue" +import {delUser, getUser} from "@/api/onlineEducation/user"; +import Cookies from "js-cookie"; +import {delStudent, getStudent} from "@/api/onlineEducation/student"; +import {getRecord} from "@/api/onlineEducation/examRecord"; + + +const { proxy } = getCurrentInstance(); +const loading = ref(false); +const dialogRef = ref(); +const data = reactive({ + queryParams: { + pageNum: 1, + pageSize: 10, + }, + total: 0, + dataList: [], + isAdmin: false + +}); + +const { queryParams, total, dataList } = toRefs(data); + +onMounted(async ()=>{ + const userInfo = JSON.parse(Cookies.get('userInfo')) + console.log("userInfo",userInfo) + if(userInfo.userType === 0){ + data.isAdmin = true; + }else { + data.isAdmin = false; + } + await getList() +}) +onUnmounted(()=>{ + +}) + +const getList = async () => { + loading.value = true + const res = await getRecord(data.queryParams) + if(res.code == 200){ + data.dataList = res.data.list + data.total = res.data.total + }else{ + ElMessage.warning(res.message) + } + loading.value = false +} + +const openDialog = (type, value) => { + dialogRef.value.openDialog(type, value); + +} + +/** 重置新增的表单以及其他数据 */ +function reset() { + proxy.resetForm("roleRef"); +} +const handleDelete = (val) => { + ElMessageBox.confirm( + '确定删除此条数据?', + '提示', + { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning', + }) + .then( async() => { + const res = await delStudent(val.id) + if(res.code == 200){ + ElMessage.success('数据删除成功') + await getList() + }else{ + ElMessage.warning(res.message) + } + }) +} </script> - - - -<style scoped lang="scss"> - -</style> -- Gitblit v1.9.2