|
@@ -0,0 +1,704 @@
|
|
|
|
|
+<!-- 考试情况统计 -->
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div class="exam-situation-card">
|
|
|
|
|
+ <div class="card-header">
|
|
|
|
|
+ <span class="card-title">考试情况统计</span>
|
|
|
|
|
+ <div class="header-right">
|
|
|
|
|
+ <el-select v-model="valueYear" @change="topChange()" size="small" style="width:220px">
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ v-for="dict in optionList"
|
|
|
|
|
+ :key="dict.value"
|
|
|
|
|
+ :label="dict.label"
|
|
|
|
|
+ :value="dict.value"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ <!-- 分组按钮 -->
|
|
|
|
|
+ <div class="table-top-button-box">
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <p @click="checkButton(2)" :class="checkIndex == 2?'check-table-button':''">研究生</p>
|
|
|
|
|
+ <p @click="checkButton(3)" :class="checkIndex == 3?'check-table-button':''">本科生</p>
|
|
|
|
|
+ <p @click="checkButton(1)" :class="checkIndex == 1?'check-table-button':''">教职工</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <!-- 导出按钮 -->
|
|
|
|
|
+ <p class="export-btn" @click="exportButton()">
|
|
|
|
|
+ <i class="el-icon-download"></i> 导出数据
|
|
|
|
|
+ </p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 图表区 -->
|
|
|
|
|
+ <div class="chart-area">
|
|
|
|
|
+ <!-- 堆叠柱状图 -->
|
|
|
|
|
+ <p class="bar-chart-wrap" v-if="!chartData.deptStats[0]" style="color:#999;font-size:14px;line-height:260px;text-align: center;">暂无数据</p>
|
|
|
|
|
+ <div class="bar-chart-wrap" v-if="chartData.deptStats[0]" id="bar-chart-wrap" ref="barChart"></div>
|
|
|
|
|
+ <!-- 环形图 -->
|
|
|
|
|
+ <p class="pie-chart-wrap" v-if="!chartData.deptStats[0]" style="color:#999;font-size:14px;line-height:260px;text-align: center;">暂无数据</p>
|
|
|
|
|
+ <div class="pie-chart-wrap" v-if="chartData.deptStats[0]" id="pie-chart-wrap" ref="pieChart"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 筛选 -->
|
|
|
|
|
+ <div class="filter-row">
|
|
|
|
|
+ <!--<el-cascader-->
|
|
|
|
|
+ <!--v-if="adminType == 1"-->
|
|
|
|
|
+ <!--style="width: 180px"-->
|
|
|
|
|
+ <!--clearable-->
|
|
|
|
|
+ <!--v-model="queryParams.deptId"-->
|
|
|
|
|
+ <!--:options="optionListA"-->
|
|
|
|
|
+ <!--:props="{-->
|
|
|
|
|
+ <!--emitPath:false,-->
|
|
|
|
|
+ <!--checkStrictly: true,-->
|
|
|
|
|
+ <!--value: 'deptId',-->
|
|
|
|
|
+ <!--label: 'deptName',-->
|
|
|
|
|
+ <!--children: 'child',-->
|
|
|
|
|
+ <!--}"-->
|
|
|
|
|
+ <!--:show-all-levels="false"-->
|
|
|
|
|
+ <!--filterable-->
|
|
|
|
|
+ <!--placeholder="筛选学院"-->
|
|
|
|
|
+ <!--@change="queryParamsChange"-->
|
|
|
|
|
+ <!--></el-cascader>-->
|
|
|
|
|
+ <el-select v-if="adminType == 1"
|
|
|
|
|
+ @change="queryParamsChange"
|
|
|
|
|
+ clearable v-model="queryParams.deptId" placeholder="学院" style="width: 180px">
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ v-for="dict in optionListA"
|
|
|
|
|
+ :key="dict.deptId"
|
|
|
|
|
+ :label="dict.deptName"
|
|
|
|
|
+ :value="dict.deptId"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ <el-select
|
|
|
|
|
+ @change="queryParamsChange"
|
|
|
|
|
+ v-model="queryParams.examTypeId" clearable placeholder="考试类型" style="width: 160px">
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ v-for="dict in optionListB"
|
|
|
|
|
+ :key="dict.id"
|
|
|
|
|
+ :label="dict.examTypeName"
|
|
|
|
|
+ :value="dict.id"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 统计表格 -->
|
|
|
|
|
+
|
|
|
|
|
+ <div class="page-content-box">
|
|
|
|
|
+ <el-table class="table-box" :data="dataList" border :span-method="objectSpanMethod">
|
|
|
|
|
+ <el-table-column prop="deptName" label="学院">
|
|
|
|
|
+ <template slot-scope="scope">
|
|
|
|
|
+ <span :style="scope.row.type == 3?'color:#184c93;font-weight:700;':''">{{scope.row.deptName}}</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column prop="examTypeName" label="考试类型" width="200">
|
|
|
|
|
+ <template slot-scope="scope">
|
|
|
|
|
+ <span :style="scope.row.type == 2?'color:#184c93;font-weight:700;':''">{{scope.row.examTypeName}}</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column prop="expectedCount" label="应考人数" width="160" align="center">
|
|
|
|
|
+ <template slot-scope="scope">
|
|
|
|
|
+ <span :style="scope.row.type == 2?'color:#184c93;font-weight:700;':''">{{scope.row.expectedCount}}</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column prop="examCount" label="实考人数" width="160" align="center">
|
|
|
|
|
+ <template slot-scope="scope">
|
|
|
|
|
+ <span :style="scope.row.type == 2?'color:#184c93;font-weight:700;':''">{{scope.row.examCount}}</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column prop="passedCount" label="通过人数" width="160" align="center">
|
|
|
|
|
+ <template slot-scope="scope">
|
|
|
|
|
+ <span :style="scope.row.type == 2?'color:#184c93;font-weight:700;':''">{{scope.row.passedCount}}</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column prop="notPassedCount" label="未通过人数" width="160" align="center">
|
|
|
|
|
+ <template slot-scope="scope">
|
|
|
|
|
+ <span :style="scope.row.type == 2?'color:#184c93;font-weight:700;':''">{{scope.row.notPassedCount}}</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column prop="absentCount" label="未考人数" width="160" align="center">
|
|
|
|
|
+ <template slot-scope="scope">
|
|
|
|
|
+ <span :style="scope.row.type == 2?'color:#184c93;font-weight:700;':''">{{scope.row.absentCount}}</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column prop="passRate" label="通过率" width="160" align="center">
|
|
|
|
|
+ <template slot-scope="scope">
|
|
|
|
|
+ <span :style="scope.row.type == 2?'color:#184c93;font-weight:700;':''">{{scope.row.passRate}}</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+ <pagination :page-sizes="[20, 30, 40, 50]"
|
|
|
|
|
+ v-show="total>0"
|
|
|
|
|
+ :total="total"
|
|
|
|
|
+ :page.sync="queryParams.page"
|
|
|
|
|
+ :limit.sync="queryParams.pageSize"
|
|
|
|
|
+ @pagination="examElStatisticsExamGroupedList"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script>
|
|
|
|
|
+import echarts from 'echarts'
|
|
|
|
|
+import {
|
|
|
|
|
+ systemUserGetPost,
|
|
|
|
|
+} from "@/api/basicsModules/index";
|
|
|
|
|
+import {
|
|
|
|
|
+ examElStatisticsGetAcademicYear,examElStatisticsExamGroupedList,examElStatisticsExamGroupedChart,
|
|
|
|
|
+ examElExamTypeList,systemDeptCurrentDept,
|
|
|
|
|
+} from "@/api/safetyEducationExaminationNew/index";
|
|
|
|
|
+export default {
|
|
|
|
|
+ name: 'ExamSituation',
|
|
|
|
|
+ data() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ adminType:0,
|
|
|
|
|
+ chartData: {
|
|
|
|
|
+ deptStats:[],
|
|
|
|
|
+ barGroups: []
|
|
|
|
|
+ },
|
|
|
|
|
+ chartList:[],
|
|
|
|
|
+ spanMap: {},
|
|
|
|
|
+ optionList:[],
|
|
|
|
|
+ optionListA:[],
|
|
|
|
|
+ optionListB:[],
|
|
|
|
|
+ valueYear:'',
|
|
|
|
|
+ checkIndex:2,
|
|
|
|
|
+ queryParams:{
|
|
|
|
|
+ page:1,
|
|
|
|
|
+ pageSize:20,
|
|
|
|
|
+ deptId:'',
|
|
|
|
|
+ examTypeId:'',
|
|
|
|
|
+ },
|
|
|
|
|
+ dataList:[],
|
|
|
|
|
+ total:0,
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ created() {
|
|
|
|
|
+
|
|
|
|
|
+ },
|
|
|
|
|
+ mounted() {
|
|
|
|
|
+ this.systemUserGetPost();
|
|
|
|
|
+ },
|
|
|
|
|
+ beforeDestroy() {
|
|
|
|
|
+ if (this._barChart) this._barChart.dispose()
|
|
|
|
|
+ if (this._pieChart) this._pieChart.dispose()
|
|
|
|
|
+ },
|
|
|
|
|
+ methods: {
|
|
|
|
|
+ //查询当前登录用户身份-校级/院级
|
|
|
|
|
+ systemUserGetPost(){
|
|
|
|
|
+ systemUserGetPost({}).then(response => {
|
|
|
|
|
+ for(let i=0;i<response.data.length;i++){
|
|
|
|
|
+ if(response.data[i].postCode == 'school_level_admin'){
|
|
|
|
|
+ if(response.data[i].isPost){
|
|
|
|
|
+ this.$set(this,'adminType','1');
|
|
|
|
|
+ }
|
|
|
|
|
+ }else if(response.data[i].postCode == 'college_admin'){
|
|
|
|
|
+ if(response.data[i].isPost){
|
|
|
|
|
+ this.$set(this,'adminType','2');
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ this.examElExamTypeList();
|
|
|
|
|
+ this.systemDeptCurrentDept();
|
|
|
|
|
+ this.examElStatisticsGetAcademicYear();
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ //导出
|
|
|
|
|
+ exportButton(){
|
|
|
|
|
+ let self = this;
|
|
|
|
|
+ self.$confirm(`确认导出数据?`, "提示", {
|
|
|
|
|
+ confirmButtonText: "确定",
|
|
|
|
|
+ cancelButtonText: "取消",
|
|
|
|
|
+ type: "warning"
|
|
|
|
|
+ }).then(async () => {
|
|
|
|
|
+ let obj = {
|
|
|
|
|
+ academicYear:this.valueYear,
|
|
|
|
|
+ personType:this.checkIndex,
|
|
|
|
|
+ examTypeId:this.queryParams.examTypeId,
|
|
|
|
|
+ };
|
|
|
|
|
+ if(this.adminType == 1){
|
|
|
|
|
+ obj.deptId = this.queryParams.deptId;
|
|
|
|
|
+ }else{
|
|
|
|
|
+ obj.deptId = localStorage.getItem('deptId');
|
|
|
|
|
+ }
|
|
|
|
|
+ self.download('/exam/elStatistics/exam/export', {...obj}, '考试情况统计.xlsx')
|
|
|
|
|
+ }).catch(() => {})
|
|
|
|
|
+ },
|
|
|
|
|
+ //图表
|
|
|
|
|
+ initBarChart() {
|
|
|
|
|
+ const groups = this.chartData.deptStats
|
|
|
|
|
+ const chart = echarts.init(this.$refs.barChart)
|
|
|
|
|
+ this._barChart = chart
|
|
|
|
|
+
|
|
|
|
|
+ const PAGE_SIZE = 6 // 每屏最多 6 个学院
|
|
|
|
|
+
|
|
|
|
|
+ // ① 展平数据,并在每个学院的分组之间插入一个空槽位(形成组间距)
|
|
|
|
|
+ const categories = []
|
|
|
|
|
+ groups.forEach((g, gi) => {
|
|
|
|
|
+ (g.examTypeGroup || []).forEach(t => {
|
|
|
|
|
+ categories.push({
|
|
|
|
|
+ deptName: g.deptName,
|
|
|
|
|
+ examTypeName: t.examTypeName,
|
|
|
|
|
+ passedCount: t.passedCount,
|
|
|
|
|
+ notPassedCount: t.notPassedCount,
|
|
|
|
|
+ absentCount: t.absentCount,
|
|
|
|
|
+ isGap: false,
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+ // 最后一个学院后面不再插空槽
|
|
|
|
|
+ if (gi < groups.length - 1) {
|
|
|
|
|
+ categories.push({ isGap: true })
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // 每个学院下的考试类型数量(不写死 3)
|
|
|
|
|
+ const examTypeCount = groups.length
|
|
|
|
|
+ ? Math.max(...groups.map(g => (g.examTypeGroup || []).length))
|
|
|
|
|
+ : 1
|
|
|
|
|
+
|
|
|
|
|
+ // ② 每屏窗口:6 个学院 × (考试类型数 + 1 个空槽) - 尾部空槽
|
|
|
|
|
+ const pageSpan = Math.min(PAGE_SIZE * (examTypeCount + 1) - 2, categories.length - 1)
|
|
|
|
|
+
|
|
|
|
|
+ // ③ 第二根轴:学院名放在每组中间位置
|
|
|
|
|
+ const middle = Math.floor(examTypeCount / 2)
|
|
|
|
|
+ const groupAxisData = categories.map((c, i) => {
|
|
|
|
|
+ if (c.isGap) return ''
|
|
|
|
|
+ const posInGroup = i % (examTypeCount + 1)
|
|
|
|
|
+ return posInGroup === middle ? c.deptName : ''
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ chart.setOption({
|
|
|
|
|
+ legend: {
|
|
|
|
|
+ data: ['通过', '未通过', '未考'],
|
|
|
|
|
+ top: 10,
|
|
|
|
|
+ itemWidth: 14,
|
|
|
|
|
+ itemHeight: 14,
|
|
|
|
|
+ textStyle: { fontSize: 12, color: '#555' },
|
|
|
|
|
+ },
|
|
|
|
|
+ grid: { top: 60, bottom: 90, left: 40, right: 10, containLabel: false },
|
|
|
|
|
+ xAxis: [
|
|
|
|
|
+ {
|
|
|
|
|
+ // 柱子定位用,不显示标签
|
|
|
|
|
+ type: 'category',
|
|
|
|
|
+ data: categories.map(c => c.examTypeName || ''),
|
|
|
|
|
+ axisLine: { show: false },
|
|
|
|
|
+ axisLabel: { show: false },
|
|
|
|
|
+ axisTick: { show: false },
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ // 只显示学院名
|
|
|
|
|
+ type: 'category',
|
|
|
|
|
+ data: groupAxisData,
|
|
|
|
|
+ position: 'bottom',
|
|
|
|
|
+ offset: 8,
|
|
|
|
|
+ axisLine: { lineStyle: { color: '#ddd' } },
|
|
|
|
|
+ axisTick: { show: false },
|
|
|
|
|
+ axisLabel: {
|
|
|
|
|
+ fontSize: 11,
|
|
|
|
|
+ color: '#666',
|
|
|
|
|
+ interval: 0,
|
|
|
|
|
+ rotate: 25,
|
|
|
|
|
+ fontWeight: 600,
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ yAxis: {
|
|
|
|
|
+ type: 'value',
|
|
|
|
|
+ splitLine: { lineStyle: { color: '#f0f0f0' } },
|
|
|
|
|
+ axisLabel: { fontSize: 11, color: '#aaa' },
|
|
|
|
|
+ axisLine: { show: false },
|
|
|
|
|
+ axisTick: { show: false },
|
|
|
|
|
+ },
|
|
|
|
|
+ dataZoom: [
|
|
|
|
|
+ {
|
|
|
|
|
+ type: 'slider',
|
|
|
|
|
+ xAxisIndex: [0, 1],
|
|
|
|
|
+ startValue: 0,
|
|
|
|
|
+ endValue: pageSpan,
|
|
|
|
|
+ height: 18,
|
|
|
|
|
+ bottom: 8,
|
|
|
|
|
+ borderColor: '#e0e0e0',
|
|
|
|
|
+ fillerColor: 'rgba(79, 190, 126, 0.15)',
|
|
|
|
|
+ handleStyle: { color: '#4fbe7e' },
|
|
|
|
|
+ moveHandleStyle: { color: '#4fbe7e' },
|
|
|
|
|
+ textStyle: { color: '#aaa', fontSize: 10 },
|
|
|
|
|
+ brushSelect: false,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ type: 'inside',
|
|
|
|
|
+ xAxisIndex: [0, 1],
|
|
|
|
|
+ startValue: 0,
|
|
|
|
|
+ endValue: pageSpan,
|
|
|
|
|
+ zoomLock: true,
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ series: [
|
|
|
|
|
+ {
|
|
|
|
|
+ name: '通过',
|
|
|
|
|
+ type: 'bar',
|
|
|
|
|
+ stack: 'total',
|
|
|
|
|
+ xAxisIndex: 0,
|
|
|
|
|
+ barMaxWidth: 40,
|
|
|
|
|
+ data: categories.map(c => (c.isGap ? null : c.passedCount)),
|
|
|
|
|
+ itemStyle: { color: '#4fbe7e' },
|
|
|
|
|
+ label: { show: true, position: 'inside', fontSize: 11, color: '#fff',
|
|
|
|
|
+ formatter: (p) => p.value > 0 ? p.value : '' },
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: '未通过',
|
|
|
|
|
+ type: 'bar',
|
|
|
|
|
+ stack: 'total',
|
|
|
|
|
+ xAxisIndex: 0,
|
|
|
|
|
+ barMaxWidth: 40,
|
|
|
|
|
+ data: categories.map(c => (c.isGap ? null : c.notPassedCount)),
|
|
|
|
|
+ itemStyle: { color: '#f56c6c' },
|
|
|
|
|
+ label: { show: true, position: 'inside', fontSize: 10, color: '#fff',
|
|
|
|
|
+ formatter: (p) => p.value > 0 ? p.value : '' },
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: '未考',
|
|
|
|
|
+ type: 'bar',
|
|
|
|
|
+ stack: 'total',
|
|
|
|
|
+ xAxisIndex: 0,
|
|
|
|
|
+ barMaxWidth: 40,
|
|
|
|
|
+ data: categories.map(c => (c.isGap ? null : c.absentCount)),
|
|
|
|
|
+ itemStyle: { color: '#c0c4cc' },
|
|
|
|
|
+ label: { show: true, position: 'inside', fontSize: 10, color: '#fff',
|
|
|
|
|
+ formatter: (p) => p.value > 0 ? p.value : '' },
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ tooltip: {
|
|
|
|
|
+ trigger: 'axis',
|
|
|
|
|
+ axisPointer: { type: 'shadow' },
|
|
|
|
|
+ formatter: (params) => {
|
|
|
|
|
+ if (!params || !params.length) return ''
|
|
|
|
|
+ const c = categories[params[0].dataIndex]
|
|
|
|
|
+ if (!c || c.isGap) return '' // 空槽位不弹提示
|
|
|
|
|
+ // let html = `<div style="font-weight:600;margin-bottom:4px">${c.deptName} · ${c.examTypeName}</div>`
|
|
|
|
|
+ let html = `<div style="font-weight:600;margin-bottom:4px">${c.examTypeName}</div>`
|
|
|
|
|
+ params.forEach(p => {
|
|
|
|
|
+ html += `<div>${p.marker}${p.seriesName}:<b>${p.value}</b></div>`
|
|
|
|
|
+ })
|
|
|
|
|
+ return html
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ //图表
|
|
|
|
|
+ initPieChart() {
|
|
|
|
|
+ const pass = this.chartData.totalPassed
|
|
|
|
|
+ const fail = this.chartData.totalNotPassed
|
|
|
|
|
+ const notExam = this.chartData.totalAbsent
|
|
|
|
|
+ const chart = echarts.init(this.$refs.pieChart)
|
|
|
|
|
+ this._pieChart = chart
|
|
|
|
|
+ chart.setOption({
|
|
|
|
|
+ legend: {
|
|
|
|
|
+ orient: 'horizontal',
|
|
|
|
|
+ bottom: 0,
|
|
|
|
|
+ data: ['通过', '未通过', '未考'],
|
|
|
|
|
+ itemWidth: 12,
|
|
|
|
|
+ itemHeight: 12,
|
|
|
|
|
+ textStyle: { fontSize: 12, color: '#555' },
|
|
|
|
|
+ },
|
|
|
|
|
+ series: [
|
|
|
|
|
+ {
|
|
|
|
|
+ type: 'pie',
|
|
|
|
|
+ radius: ['45%', '68%'],
|
|
|
|
|
+ center: ['50%', '45%'],
|
|
|
|
|
+ label: {
|
|
|
|
|
+ formatter: '{b}',
|
|
|
|
|
+ fontSize: 12,
|
|
|
|
|
+ color: '#555',
|
|
|
|
|
+ },
|
|
|
|
|
+ labelLine: { length: 12, length2: 8 },
|
|
|
|
|
+ data: [
|
|
|
|
|
+ { value: pass, name: '通过', itemStyle: { color: '#4fbe7e' } },
|
|
|
|
|
+ { value: fail, name: '未通过', itemStyle: { color: '#f56c6c' } },
|
|
|
|
|
+ { value: notExam, name: '未考', itemStyle: { color: '#c0c4cc' } },
|
|
|
|
|
+ ],
|
|
|
|
|
+ emphasis: {
|
|
|
|
|
+ itemStyle: { shadowBlur: 6, shadowOffsetX: 0, shadowColor: 'rgba(0,0,0,0.2)' }
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ //顶部学年切换
|
|
|
|
|
+ topChange(){
|
|
|
|
|
+ this.$set(this.queryParams,'page',1);
|
|
|
|
|
+ this.examElStatisticsExamGroupedChart();
|
|
|
|
|
+ this.examElStatisticsExamGroupedList();
|
|
|
|
|
+ },
|
|
|
|
|
+ //身份切换
|
|
|
|
|
+ checkButton(val){
|
|
|
|
|
+ if(this.checkIndex!=val){
|
|
|
|
|
+ this.$set(this,'checkIndex',val);
|
|
|
|
|
+ this.$set(this.queryParams,'page',1);
|
|
|
|
|
+ this.examElStatisticsExamGroupedChart();
|
|
|
|
|
+ this.examElStatisticsExamGroupedList();
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ //条件变更
|
|
|
|
|
+ queryParamsChange(){
|
|
|
|
|
+ this.$set(this.queryParams,'page',1);
|
|
|
|
|
+ this.examElStatisticsExamGroupedChart();
|
|
|
|
|
+ this.examElStatisticsExamGroupedList();
|
|
|
|
|
+ },
|
|
|
|
|
+ //数据合并
|
|
|
|
|
+ objectSpanMethod({ row, column, rowIndex, columnIndex }) {
|
|
|
|
|
+ // 只对第一列(学院列,columnIndex === 0)进行合并
|
|
|
|
|
+ if (columnIndex === 0) {
|
|
|
|
|
+ const list = this.dataList;
|
|
|
|
|
+
|
|
|
|
|
+ // 如果是第一行,或者当前行的学院与上一行的学院不同,则开始合并计算
|
|
|
|
|
+ if (rowIndex === 0 || row.deptName !== list[rowIndex - 1].deptName) {
|
|
|
|
|
+ let rowspan = 1;
|
|
|
|
|
+ // 向下遍历,统计有多少行是相同的学院
|
|
|
|
|
+ for (let i = rowIndex + 1; i < list.length; i++) {
|
|
|
|
|
+ if (list[i].deptName === row.deptName) {
|
|
|
|
|
+ rowspan++;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ break; // 遇到不同的学院,停止计算
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return { rowspan, colspan: 1 };
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 如果当前行的学院与上一行相同,则隐藏当前单元格(被合并了)
|
|
|
|
|
+ return { rowspan: 0, colspan: 0 };
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // 其他列正常显示
|
|
|
|
|
+ return { rowspan: 1, colspan: 1 };
|
|
|
|
|
+ },
|
|
|
|
|
+ //考试统计图表
|
|
|
|
|
+ examElStatisticsExamGroupedChart(){
|
|
|
|
|
+ let obj = {
|
|
|
|
|
+ academicYear:this.valueYear,
|
|
|
|
|
+ personType:this.checkIndex,
|
|
|
|
|
+ }
|
|
|
|
|
+ if(this.adminType == 1){
|
|
|
|
|
+ obj.deptId = this.queryParams.deptId;
|
|
|
|
|
+ }else if(this.adminType == 2){
|
|
|
|
|
+ obj.deptId = localStorage.getItem('deptId');
|
|
|
|
|
+ }
|
|
|
|
|
+ examElStatisticsExamGroupedChart(obj).then(response => {
|
|
|
|
|
+ this.$set(this,'chartData',response.data);
|
|
|
|
|
+ this.$nextTick(() => {
|
|
|
|
|
+ if(response.data.deptStats[0]){
|
|
|
|
|
+ this.$nextTick(() => {
|
|
|
|
|
+ this.initBarChart()
|
|
|
|
|
+ this.initPieChart()
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ //考试统计列表
|
|
|
|
|
+ examElStatisticsExamGroupedList(){
|
|
|
|
|
+ let obj = {
|
|
|
|
|
+ page:this.queryParams.page,
|
|
|
|
|
+ pageSize:this.queryParams.pageSize,
|
|
|
|
|
+ academicYear:this.valueYear,
|
|
|
|
|
+ personType:this.checkIndex,
|
|
|
|
|
+ examTypeId:this.queryParams.examTypeId,
|
|
|
|
|
+ }
|
|
|
|
|
+ if(this.adminType == 1){
|
|
|
|
|
+ obj.deptId = this.queryParams.deptId;
|
|
|
|
|
+ }else{
|
|
|
|
|
+ obj.deptId = localStorage.getItem('deptId');
|
|
|
|
|
+ }
|
|
|
|
|
+ examElStatisticsExamGroupedList(obj).then(response => {
|
|
|
|
|
+ // this.$set(this,'dataList',response.data.records);
|
|
|
|
|
+ let list = [];
|
|
|
|
|
+ for(let i=0;i<response.data.deptStats.length;i++){
|
|
|
|
|
+ for(let o=0;o<response.data.deptStats[i].examTypeGroup.length;o++){
|
|
|
|
|
+ list.push({
|
|
|
|
|
+ deptName:response.data.deptStats[i].deptName,
|
|
|
|
|
+ examTypeName:response.data.deptStats[i].examTypeGroup[o].examTypeName,
|
|
|
|
|
+ expectedCount:response.data.deptStats[i].examTypeGroup[o].expectedCount,
|
|
|
|
|
+ examCount:response.data.deptStats[i].examTypeGroup[o].examCount,
|
|
|
|
|
+ passedCount:response.data.deptStats[i].examTypeGroup[o].passedCount,
|
|
|
|
|
+ notPassedCount:response.data.deptStats[i].examTypeGroup[o].notPassedCount,
|
|
|
|
|
+ absentCount:response.data.deptStats[i].examTypeGroup[o].absentCount,
|
|
|
|
|
+ passRate:response.data.deptStats[i].examTypeGroup[o].passRate,
|
|
|
|
|
+ type:1,
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ list.push({
|
|
|
|
|
+ deptName:response.data.deptStats[i].deptName,
|
|
|
|
|
+ examTypeName:'汇总',
|
|
|
|
|
+ expectedCount:response.data.deptStats[i].summary.expectedCount,
|
|
|
|
|
+ examCount:response.data.deptStats[i].summary.examCount,
|
|
|
|
|
+ passedCount:response.data.deptStats[i].summary.passedCount,
|
|
|
|
|
+ notPassedCount:response.data.deptStats[i].summary.notPassedCount,
|
|
|
|
|
+ absentCount:response.data.deptStats[i].summary.absentCount,
|
|
|
|
|
+ passRate:response.data.deptStats[i].summary.passRate,
|
|
|
|
|
+ type:2,
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for(let o=0;o<response.data.schoolStats.examTypeGroup.length;o++){
|
|
|
|
|
+ list.push({
|
|
|
|
|
+ deptName:response.data.schoolStats.deptName,
|
|
|
|
|
+ examTypeName:response.data.schoolStats.examTypeGroup[o].examTypeName,
|
|
|
|
|
+ expectedCount:response.data.schoolStats.examTypeGroup[o].expectedCount,
|
|
|
|
|
+ examCount:response.data.schoolStats.examTypeGroup[o].examCount,
|
|
|
|
|
+ passedCount:response.data.schoolStats.examTypeGroup[o].passedCount,
|
|
|
|
|
+ notPassedCount:response.data.schoolStats.examTypeGroup[o].notPassedCount,
|
|
|
|
|
+ absentCount:response.data.schoolStats.examTypeGroup[o].absentCount,
|
|
|
|
|
+ passRate:response.data.schoolStats.examTypeGroup[o].passRate,
|
|
|
|
|
+ type:3,
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ list.push({
|
|
|
|
|
+ deptName:response.data.schoolStats.deptName,
|
|
|
|
|
+ examTypeName:'汇总',
|
|
|
|
|
+ expectedCount:response.data.schoolStats.summary.expectedCount,
|
|
|
|
|
+ examCount:response.data.schoolStats.summary.examCount,
|
|
|
|
|
+ passedCount:response.data.schoolStats.summary.passedCount,
|
|
|
|
|
+ notPassedCount:response.data.schoolStats.summary.notPassedCount,
|
|
|
|
|
+ absentCount:response.data.schoolStats.summary.absentCount,
|
|
|
|
|
+ passRate:response.data.schoolStats.summary.passRate,
|
|
|
|
|
+ type:2,
|
|
|
|
|
+ })
|
|
|
|
|
+ this.$set(this,'dataList',list);
|
|
|
|
|
+ this.$set(this,'total',response.data.total);
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ //查询全部考试类型
|
|
|
|
|
+ examElExamTypeList(){
|
|
|
|
|
+ examElExamTypeList({}).then(response => {
|
|
|
|
|
+ let list = [];
|
|
|
|
|
+ for(let i=0;i<response.data.length;i++){
|
|
|
|
|
+ if(response.data[i].examTypeCode == 'CLASSIFIED' || response.data[i].examTypeCode == 'yzyh' || response.data[i].examTypeCode == 'STUDENT'){
|
|
|
|
|
+ list.push(response.data[i]);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ this.$set(this,'optionListB',list);
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ //学院列表
|
|
|
|
|
+ systemDeptCurrentDept(){
|
|
|
|
|
+ systemDeptCurrentDept({}).then(response => {
|
|
|
|
|
+ const list = response.data || [];
|
|
|
|
|
+ this.formatTreeData(list);
|
|
|
|
|
+ if(list[0]){
|
|
|
|
|
+ this.$set(this,'optionListA',list[0].child);
|
|
|
|
|
+ }else{
|
|
|
|
|
+ this.$set(this,'optionListA',[]);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ //学年列表
|
|
|
|
|
+ examElStatisticsGetAcademicYear(){
|
|
|
|
|
+ examElStatisticsGetAcademicYear({}).then(response => {
|
|
|
|
|
+ this.$set(this,'optionList',response.data);
|
|
|
|
|
+ this.$set(this,'valueYear',response.data[0].value);
|
|
|
|
|
+ this.examElStatisticsExamGroupedList();
|
|
|
|
|
+ this.examElStatisticsExamGroupedChart();
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped lang="scss">
|
|
|
|
|
+.exam-situation-card {
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ /*border-radius: 6px;*/
|
|
|
|
|
+ padding: 20px;
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
|
+ /*box-shadow: 0 1px 4px rgba(0,0,0,0.06);*/
|
|
|
|
|
+ background: rgba(255, 255, 255, 0.95);
|
|
|
|
|
+ border: 1px solid #e8edf5;
|
|
|
|
|
+ border-radius: 16px;
|
|
|
|
|
+ box-shadow: 0 4px 20px rgba(37, 50, 74, 0.06);
|
|
|
|
|
+
|
|
|
|
|
+ .card-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ margin-bottom: 16px;
|
|
|
|
|
+ .card-title { font-size: 16px; font-weight: 700; color: #222; }
|
|
|
|
|
+ .header-right { width:610px;display: flex; align-items: center; gap: 20px; }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* 分组筛选按钮 */
|
|
|
|
|
+ .table-top-button-box {
|
|
|
|
|
+ display: inline-block;
|
|
|
|
|
+ vertical-align: top;
|
|
|
|
|
+ div {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ p {
|
|
|
|
|
+ border: 1px solid #DCDFE6;
|
|
|
|
|
+ width: 80px;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ height: 40px;
|
|
|
|
|
+ line-height: 40px;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ &:nth-child(1) { border-top-left-radius: 4px; border-bottom-left-radius: 4px; }
|
|
|
|
|
+ &:nth-child(2) { border-left: none; border-right: none; }
|
|
|
|
|
+ &:nth-child(3) { border-top-right-radius: 4px; border-bottom-right-radius: 4px; }
|
|
|
|
|
+ }
|
|
|
|
|
+ .check-table-button{
|
|
|
|
|
+ border: 1px solid #0183fa!important;
|
|
|
|
|
+ background-color: #0183fa!important;;
|
|
|
|
|
+ color:#fff!important;;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* 导出按钮 */
|
|
|
|
|
+ .export-btn {
|
|
|
|
|
+ display: inline-block;
|
|
|
|
|
+ padding: 0 16px;
|
|
|
|
|
+ height: 40px;
|
|
|
|
|
+ line-height: 40px;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ background-color: #0183fa;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ border: 1px solid #0183fa;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .chart-area {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 0;
|
|
|
|
|
+ margin-bottom: 16px;
|
|
|
|
|
+ border: 1px solid #eee;
|
|
|
|
|
+ border-radius: 6px;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+
|
|
|
|
|
+ .bar-chart-wrap {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ height: 280px;
|
|
|
|
|
+ border-right: 1px solid #eee;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .pie-chart-wrap {
|
|
|
|
|
+ width: 320px;
|
|
|
|
|
+ height: 280px;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .filter-row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 20px;
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .page-content-box{
|
|
|
|
|
+ height:500px;
|
|
|
|
|
+ padding:0;
|
|
|
|
|
+ ::v-deep .el-table--medium td:nth-child(1){
|
|
|
|
|
+ border-left:1px solid #dfe6ec!important;
|
|
|
|
|
+ }
|
|
|
|
|
+ ::v-deep .el-table__row:nth-child(4n){
|
|
|
|
|
+ /*border-left:none!important;*/
|
|
|
|
|
+ /*color:#184c93;*/
|
|
|
|
|
+ /*background-color: #edf5ff;*/
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .link-blue { color: #409eff; }
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|