535 lines
21 KiB
Vue
535 lines
21 KiB
Vue
<template>
|
||
<div class="app-container">
|
||
<el-card class="box-card">
|
||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" label-width="120px">
|
||
<el-form-item label="报告" prop="relId">
|
||
<el-select v-model="queryParams.relId" placeholder="请选择报告">
|
||
<el-option v-for="item in relList" :key="item.relId" :label="item.reportName"
|
||
:value="item.relId"></el-option>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label=" ">
|
||
<el-button type="primary" icon="el-icon-search" plain @click="queryData">查询</el-button>
|
||
<el-button type="success" icon="el-icon-view" plain @click="lookVClick">查看学生数据</el-button>
|
||
</el-form-item>
|
||
<el-form-item label="班级" prop="classId">
|
||
<el-select v-model="queryParams.classId" placeholder="请选择班级" filterable clearable>
|
||
<el-option v-for="(v, i) in search_class_list" :key="i" :label="v.className" :value="v.classId">
|
||
</el-option>
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-form>
|
||
</el-card>
|
||
<el-row :gutter="20" class="mt20">
|
||
<el-col :span="12">
|
||
<el-card class="box-card">
|
||
<div slot="header" class="clearfix">
|
||
<span>报告达成率(报告的全部指标都达成的情况)</span>
|
||
<br />
|
||
<span style="font-size: 10px;color: red;">
|
||
当前数据:{{ isEmpty(searchRelName) ? "" : searchRelName }}--
|
||
{{ isEmpty(searchClassName) ? "" : searchClassName }}
|
||
</span>
|
||
</div>
|
||
<div class="chart-container">
|
||
<div ref="reportFinishChart" style="height: 400px"></div>
|
||
</div>
|
||
</el-card>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-card class="box-card">
|
||
<div slot="header" class="clearfix">
|
||
<span>指标达成率(学生达成了多少个指标)</span>
|
||
<br />
|
||
<span style="font-size: 10px;color: red;">
|
||
当前数据:{{ isEmpty(searchRelName) ? "" : searchRelName }}--
|
||
{{ isEmpty(searchClassName) ? "" : searchClassName }}
|
||
</span>
|
||
</div>
|
||
<div class="chart-container">
|
||
<div ref="itemFinishChart" style="height: 400px"></div>
|
||
</div>
|
||
</el-card>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
|
||
<el-row :gutter="20" class="mt20">
|
||
<el-col :span="12">
|
||
<el-card class="box-card">
|
||
<div slot="header" class="clearfix">
|
||
<span>各班级报告达成率</span>
|
||
<el-button @click='exportClassFinishList()' style="float: right;" icon="el-icon-download"
|
||
type="text">导出</el-button>
|
||
<br />
|
||
<span style="font-size: 10px;color: red;">
|
||
当前数据:{{ isEmpty(searchRelName) ? "" : searchRelName }}
|
||
</span>
|
||
</div>
|
||
<div class="chart-container">
|
||
<el-table :data="pagedClassFinishList">
|
||
<el-table-column label="班级" width="140" align="center" prop="class_name" />
|
||
<el-table-column label="在校生数" align="center" prop="stu_num" />
|
||
<el-table-column label="达成数" align="center" prop="finish_num" />
|
||
<el-table-column label="达成率" align="center">
|
||
<template slot-scope="scope">
|
||
{{
|
||
scope.row.stu_num
|
||
? ((scope.row.finish_num / scope.row.stu_num) * 100).toFixed(4)
|
||
: '0.0000'
|
||
}}%
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<el-pagination background layout="sizes, prev, pager, next"
|
||
:current-page="currentPageClassFinish" :page-size="pageSizeClassFinish"
|
||
:total="classFinishList.length" @current-change="handleClassFinishPageChange"
|
||
@size-change="handleClassFinishSizeChange" :page-sizes="[10, 20, 50, 100]" :pager-count="5"
|
||
style="text-align: right; margin-top: 20px;" />
|
||
</div>
|
||
</el-card>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-card class="box-card">
|
||
<div slot="header" class="clearfix">
|
||
<span>各班级指标达成率</span>
|
||
<el-button @click='exportClassItemFinishList()' style="float: right;" icon="el-icon-download"
|
||
type="text">导出</el-button>
|
||
<br />
|
||
<span style="font-size: 10px;color: red;">
|
||
当前数据:{{ isEmpty(searchRelName) ? "" : searchRelName }}
|
||
</span>
|
||
</div>
|
||
<div class="chart-container">
|
||
<el-table :data="pagedClassItemFinishList">
|
||
<el-table-column label="班级" width="140" align="center" prop="class_name" />
|
||
<el-table-column label="在校生数" align="center" prop="stu_num" />
|
||
<el-table-column label="总指标数" align="center" prop="total_num" />
|
||
<el-table-column label="达成数" align="center" prop="finish_num" />
|
||
<el-table-column label="达成率" align="center">
|
||
<template slot-scope="scope">
|
||
{{
|
||
scope.row.total_num
|
||
? ((scope.row.finish_num / scope.row.total_num) * 100).toFixed(4)
|
||
: '0.0000'
|
||
}}%
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<el-pagination background layout="sizes, prev, pager, next"
|
||
:current-page="currentPageClassItemFinish" :page-size="pageSizeClassItemFinish"
|
||
:total="classItemFinishList.length" @current-change="handleClassItemFinishPageChange"
|
||
@size-change="handleClassItemFinishSizeChange" :page-sizes="[10, 20, 50, 100]"
|
||
:pager-count="5" style="text-align: right; margin-top: 20px;" />
|
||
</div>
|
||
</el-card>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<el-card class="box-card">
|
||
<div slot="header" class="clearfix">
|
||
<span>指标达成情况</span>
|
||
<el-button @click='exportTable("TotalForm", searchRelName
|
||
+ "--" + searchClassName + "指标达成情况")' style="float: right;" icon="el-icon-download"
|
||
type="text">导出</el-button>
|
||
<br />
|
||
<span style="font-size: 10px;color: red;">
|
||
当前数据:{{ isEmpty(searchRelName) ? "" : searchRelName }}--
|
||
{{ isEmpty(searchClassName) ? "" : searchClassName }}
|
||
</span>
|
||
</div>
|
||
<TotalForm id="TotalForm" v-if="tableV" :tableData="tableData" />
|
||
</el-card>
|
||
|
||
<table id="tempTable">
|
||
|
||
</table>
|
||
|
||
<el-dialog :visible.sync="lookV" fullscreen append-to-body>
|
||
<FdyList v-if="lookV" :reportData="lookData" />
|
||
</el-dialog>
|
||
|
||
</div>
|
||
</template>
|
||
<script>
|
||
import {
|
||
countFdyItemFinishByRelId as countItemFinishByRelId,
|
||
countFdyFinishNumByRelId as countFinishNumByRelId,
|
||
countFdyItemFinishNumByRelId as countItemFinishNumByRelId,
|
||
countFdyClassFinishNumByRelId as countClassFinishNumByRelId,
|
||
countFdyClassItemFinishNumByRelId as countClassItemFinishNumByRelId
|
||
} from "@/api/diagnostic/reportStandards";
|
||
|
||
import { listAllRelease } from '@/api/diagnostic/DiaReportRelease';
|
||
|
||
import { listOwnClass as listClass } from "@/api/stuCQS/info-fill/stu_eva_task";
|
||
|
||
import { isEmpty, fullLoading } from '@/api/helpFunc';
|
||
|
||
import TotalForm from './cpnt/TotalForm';
|
||
import FdyList from "./cpnt/FdyList.vue";
|
||
|
||
import * as echarts from 'echarts';
|
||
import XLSX from 'xlsx';
|
||
|
||
export default {
|
||
name: "DataView",
|
||
components: {
|
||
TotalForm,
|
||
FdyList
|
||
},
|
||
|
||
data() {
|
||
return {
|
||
isEmpty,
|
||
|
||
queryParams: {
|
||
relId: null
|
||
},
|
||
relList: [],
|
||
gradeList: [],
|
||
search_major_list: [],
|
||
search_class_list: [],
|
||
|
||
|
||
tableData: [],
|
||
tableV: false,
|
||
|
||
reportFinishChart: null,
|
||
itemFinishChart: null,
|
||
|
||
majorItemFinishList: [],
|
||
majorFinishList: [],
|
||
|
||
gradeItemFinishList: [],
|
||
gradeFinishList: [],
|
||
|
||
classItemFinishList: [],
|
||
classFinishList: [],
|
||
|
||
searchRelName: "",
|
||
searchMajorName: "",
|
||
searchClassName: "",
|
||
searchGradeName: "",
|
||
|
||
currentPageMajorFinish: 1,
|
||
pageSizeMajorFinish: 10,
|
||
|
||
currentPageMajorItemFinish: 1,
|
||
pageSizeMajorItemFinish: 10,
|
||
|
||
currentPageClassFinish: 1,
|
||
pageSizeClassFinish: 10,
|
||
|
||
currentPageClassItemFinish: 1,
|
||
pageSizeClassItemFinish: 10,
|
||
|
||
lookV: false,
|
||
lookData: {},
|
||
|
||
}
|
||
},
|
||
created() {
|
||
this.listAllRelease();
|
||
this.listClass();
|
||
},
|
||
mounted() {
|
||
this.initCharts();
|
||
},
|
||
computed: {
|
||
pagedClassFinishList() {
|
||
const start = (this.currentPageClassFinish - 1) * this.pageSizeClassFinish;
|
||
const end = start + this.pageSizeClassFinish;
|
||
return this.classFinishList.slice(start, end);
|
||
},
|
||
pagedClassItemFinishList() {
|
||
const start = (this.currentPageClassItemFinish - 1) * this.pageSizeClassItemFinish;
|
||
const end = start + this.pageSizeClassItemFinish;
|
||
return this.classItemFinishList.slice(start, end);
|
||
},
|
||
},
|
||
methods: {
|
||
lookVClick() {
|
||
if (isEmpty(this.queryParams.relId)) {
|
||
this.$message.warning("请先选择报告");
|
||
return;
|
||
}
|
||
this.lookData = {};
|
||
let reportName = this.relList.find(item => item.relId == this.queryParams.relId).reportName;
|
||
let reportId = this.relList.find(item => item.relId == this.queryParams.relId).reportId;
|
||
let sdata = {
|
||
relId: this.queryParams.relId,
|
||
reportName,
|
||
reportId
|
||
}
|
||
this.lookData = sdata;
|
||
this.lookV = true;
|
||
},
|
||
exportClassFinishList() {
|
||
let classFinishList = [...this.classFinishList];
|
||
const data = [
|
||
['学院', '专业', '班级', '在校生数', '达成数', '达成率']
|
||
];
|
||
|
||
classFinishList.forEach(row => {
|
||
data.push([
|
||
row.dept_name,
|
||
row.major_name,
|
||
row.class_name,
|
||
row.stu_num,
|
||
row.finish_num,
|
||
row.stu_num
|
||
? ((row.finish_num / row.stu_num) * 100).toFixed(4) + '%'
|
||
: '0.0000%'
|
||
]);
|
||
});
|
||
const worksheet = XLSX.utils.aoa_to_sheet(data);
|
||
const workbook = XLSX.utils.book_new();
|
||
XLSX.utils.book_append_sheet(workbook, worksheet, '各班级报告达成统计');
|
||
|
||
let title = this.searchRelName + "--" + this.searchMajorName;
|
||
XLSX.writeFile(workbook, title + '各班级报告达成统计.xlsx');
|
||
},
|
||
exportClassItemFinishList() {
|
||
let classItemFinishList = [...this.classItemFinishList];
|
||
const data = [
|
||
['学院', '专业', '班级', '在校生数', '总指标数', '达成数', '达成率']
|
||
];
|
||
|
||
classItemFinishList.forEach(row => {
|
||
data.push([
|
||
row.dept_name,
|
||
row.major_name,
|
||
row.class_name,
|
||
row.stu_num,
|
||
row.total_num,
|
||
row.finish_num,
|
||
row.total_num
|
||
? ((row.finish_num / row.total_num) * 100).toFixed(4) + '%'
|
||
: '0.0000%'
|
||
]);
|
||
});
|
||
const worksheet = XLSX.utils.aoa_to_sheet(data);
|
||
const workbook = XLSX.utils.book_new();
|
||
XLSX.utils.book_append_sheet(workbook, worksheet, '各班级指标达成统计');
|
||
|
||
let title = this.searchRelName + "--" + this.searchMajorName;
|
||
XLSX.writeFile(workbook, title + '各班级指标达成统计.xlsx');
|
||
},
|
||
|
||
exportTable(id, title) {
|
||
let thead = document.getElementById(id).getElementsByTagName('thead')[0];
|
||
let tbody = document.getElementById(id).getElementsByTagName('tbody')[0];
|
||
let tempTable = document.getElementById('tempTable');
|
||
if (thead) {
|
||
tempTable.appendChild(thead.cloneNode(true))
|
||
}
|
||
if (tbody) {
|
||
Array.from(tbody.getElementsByTagName('td')).forEach(td => {
|
||
if (td.innerText.trim().endsWith('%')) {
|
||
td.innerHTML = `<span style="mso-number-format:'\\@';">${td.innerText}</span>`;
|
||
}
|
||
});
|
||
tempTable.appendChild(tbody.cloneNode(true))
|
||
}
|
||
const wb = XLSX.utils.table_to_book(tempTable, { sheet: 'Sheet1', cellText: true })
|
||
XLSX.writeFile(wb, title + ".xlsx");
|
||
tempTable.innerHTML = '';
|
||
},
|
||
|
||
handleClassItemFinishPageChange(page) {
|
||
this.currentPageClassItemFinish = page;
|
||
},
|
||
handleClassItemFinishSizeChange(size) {
|
||
this.pageSizeClassItemFinish = size;
|
||
this.currentPageClassItemFinish = 1;
|
||
},
|
||
handleClassFinishPageChange(page) {
|
||
this.currentPageClassFinish = page;
|
||
},
|
||
handleClassFinishSizeChange(size) {
|
||
this.pageSizeClassFinish = size;
|
||
this.currentPageClassFinish = 1;
|
||
},
|
||
|
||
async queryData() {
|
||
let sdata = { ...this.queryParams };
|
||
if (isEmpty(sdata.relId)) {
|
||
this.$message.info("请选择报告");
|
||
return;
|
||
}
|
||
|
||
this.searchRelName = this.relList.find(item => item.relId == sdata.relId).reportName;
|
||
|
||
if (!isEmpty(sdata.classId)) {
|
||
this.searchClassName = this.search_class_list.find(item => item.classId == sdata.classId).className;
|
||
}
|
||
|
||
// 只开一次 loading
|
||
let loading = fullLoading(this);
|
||
// 并行等待接口
|
||
try {
|
||
await Promise.all([
|
||
this.countFinishNumByRelId(true), // 传入参数表示不在内部开loading
|
||
this.countItemFinishByRelId(true),
|
||
this.countItemFinishNumByRelId(true),
|
||
this.countClassFinishNumByRelId(true),
|
||
this.countClassItemFinishNumByRelId(true),
|
||
]);
|
||
} finally {
|
||
loading.close();
|
||
}
|
||
},
|
||
async countClassFinishNumByRelId(noLoading) {
|
||
let loading;
|
||
if (!noLoading) loading = fullLoading(this);
|
||
let res = await countClassFinishNumByRelId(this.queryParams);
|
||
if (loading) loading.close();
|
||
if (res.code == 200) {
|
||
this.classFinishList = [...res.data];
|
||
}
|
||
},
|
||
async countClassItemFinishNumByRelId(noLoading) {
|
||
let loading;
|
||
if (!noLoading) loading = fullLoading(this);
|
||
let res = await countClassItemFinishNumByRelId(this.queryParams);
|
||
if (loading) loading.close();
|
||
if (res.code == 200) {
|
||
this.classItemFinishList = [...res.data];
|
||
}
|
||
},
|
||
|
||
|
||
async countItemFinishNumByRelId(noLoading) {
|
||
let loading;
|
||
if (!noLoading) loading = fullLoading(this);
|
||
let res = await countItemFinishNumByRelId(this.queryParams);
|
||
if (loading) loading.close();
|
||
if (res.code == 200) {
|
||
let temp = { ...res.data };
|
||
let data = [];
|
||
data.push({ value: temp.finish_num, name: '已达成' });
|
||
data.push({ value: temp.total_num - temp.finish_num, name: '未达成' });
|
||
this.renderPieChart(this.itemFinishChart, '指标达成率', data);
|
||
}
|
||
},
|
||
async countFinishNumByRelId(noLoading) {
|
||
let loading;
|
||
if (!noLoading) loading = fullLoading(this);
|
||
let res = await countFinishNumByRelId(this.queryParams);
|
||
if (loading) loading.close();
|
||
if (res.code == 200) {
|
||
let temp = { ...res.data };
|
||
let data = [];
|
||
data.push({ value: temp.finish_num, name: '已达成' });
|
||
data.push({ value: temp.stu_num - temp.finish_num, name: '未达成' });
|
||
this.renderPieChart(this.reportFinishChart, '报告达成率', data);
|
||
}
|
||
},
|
||
|
||
async countItemFinishByRelId(noLoading) {
|
||
let loading;
|
||
if (!noLoading) loading = fullLoading(this);
|
||
let sdata = { ...this.queryParams };
|
||
this.tableV = false;
|
||
let res = await countItemFinishByRelId(sdata);
|
||
if (loading) loading.close();
|
||
if (res.code == 200) {
|
||
this.tableV = true;
|
||
this.tableData = [...res.data];
|
||
}
|
||
},
|
||
renderPieChart(chart, title, data) {
|
||
// 添加数据检查
|
||
if (!data || !Array.isArray(data)) {
|
||
console.warn('数据格式不正确或为空:', data);
|
||
data = []; // 设置为空数组,避免报错
|
||
}
|
||
|
||
const option = {
|
||
title: {
|
||
text: title,
|
||
left: 'center'
|
||
},
|
||
tooltip: {
|
||
trigger: 'item',
|
||
formatter: '{a} <br/>{b}: {c} ({d}%)'
|
||
},
|
||
legend: {
|
||
orient: 'vertical',
|
||
left: 'left',
|
||
},
|
||
series: [
|
||
{
|
||
name: '达成情况',
|
||
type: 'pie',
|
||
radius: '50%',
|
||
center: ['70%', '50%'],
|
||
data: [...data],
|
||
emphasis: {
|
||
itemStyle: {
|
||
shadowBlur: 10,
|
||
shadowOffsetX: 0,
|
||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||
}
|
||
}
|
||
}
|
||
]
|
||
}
|
||
|
||
// 如果数据为空,显示暂无数据
|
||
if (data.length === 0) {
|
||
option.title.subtext = '暂无数据';
|
||
}
|
||
|
||
chart.setOption(option);
|
||
},
|
||
initCharts() {
|
||
if (this.$refs.reportFinishChart && this.$refs.itemFinishChart) {
|
||
this.reportFinishChart = echarts.init(this.$refs.reportFinishChart);
|
||
this.itemFinishChart = echarts.init(this.$refs.itemFinishChart);
|
||
}
|
||
},
|
||
async listClass() {
|
||
let res = await listClass();
|
||
if (res.code == 200) {
|
||
this.search_class_list = [...res.data];
|
||
}
|
||
},
|
||
|
||
async listAllRelease() {
|
||
let res = await listAllRelease();
|
||
if (res.code == 200) {
|
||
this.relList = [...res.data];
|
||
}
|
||
},
|
||
|
||
}
|
||
}
|
||
</script>
|
||
<style scoped lang="scss">
|
||
.box-card {
|
||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.chart-container {
|
||
background: #fff;
|
||
padding: 10px;
|
||
border-radius: 5px;
|
||
}
|
||
|
||
.mt20 {
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.clearfix:before,
|
||
.clearfix:after {
|
||
display: table;
|
||
content: '';
|
||
}
|
||
|
||
.clearfix:after {
|
||
clear: both;
|
||
}
|
||
</style>
|