Files
zhxg_pc/src/views/diagnostic/DataView/fdy.vue
2025-07-28 15:52:07 +08:00

535 lines
21 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>