2105 lines
68 KiB
Vue
2105 lines
68 KiB
Vue
<template>
|
|
<div class="workOrder">
|
|
<!-- 报告类型 Tab 栏 -->
|
|
<div class="reportHeader">
|
|
<div class="tabBar">
|
|
<div class="tabItem" :class="{ active: activeTab === 'grade' }" @click="switchTab('grade')">年级报告</div>
|
|
<div class="tabItem" :class="{ active: activeTab === 'class' }" @click="switchTab('class')">班级报告</div>
|
|
<div class="tabItem" :class="{ active: activeTab === 'teacher' }" @click="switchTab('teacher')">教师报告</div>
|
|
<div class="tabItem" :class="{ active: activeTab === 'student' }" @click="switchTab('student')">学生报告</div>
|
|
</div>
|
|
<div class="tabFilter">
|
|
<template v-if="activeTab === 'grade'">
|
|
<span class="filterLabel">年级:</span>
|
|
<el-select v-model="gradeTabId" placeholder="请选择年级" style="width:200px"
|
|
@change="onGradeTabChange" clearable>
|
|
<el-option v-for="item in data.gradeList" :key="item.id" :label="item.gradeName"
|
|
:value="item.id" />
|
|
</el-select>
|
|
</template>
|
|
<template v-else-if="activeTab === 'class'">
|
|
<span class="filterLabel">年级-班级:</span>
|
|
<el-cascader v-model="classTabId" :options="data.classes" clearable placeholder="请选择班级"
|
|
style="width:260px" @change="onClassTabChange" />
|
|
</template>
|
|
<template v-else-if="activeTab === 'teacher'">
|
|
<span class="filterLabel">教师:</span>
|
|
<el-select v-model="teacherTabId" placeholder="请选择教师" style="width:200px" filterable
|
|
@change="onTeacherTabChange">
|
|
<el-option v-for="item in data.teacherList" :key="item.id" :label="item.teacherName"
|
|
:value="item.id" />
|
|
</el-select>
|
|
</template>
|
|
<template v-else-if="activeTab === 'student'">
|
|
<span class="filterLabel">年级-班级:</span>
|
|
<el-cascader v-model="studentClassTabId" :options="data.classes" clearable placeholder="请先选择班级"
|
|
style="width:260px" @change="onStudentClassChange" />
|
|
<template v-if="studentList.length > 0">
|
|
<span class="filterLabel" style="margin-left:16px">学生:</span>
|
|
<el-select v-model="studentTabId" placeholder="请选择学生" style="width:200px" filterable
|
|
@change="onStudentTabChange">
|
|
<el-option v-for="item in studentList" :key="item.studentNo || item.StudentNo"
|
|
:label="item.studentName" :value="item" />
|
|
</el-select>
|
|
</template>
|
|
<el-icon v-if="studentListLoading" class="is-loading" style="margin-left:8px"><loading /></el-icon>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
<!-- 统计卡片 -->
|
|
<template v-if="activeTab === 'grade'">
|
|
<div class="list" v-if="loading.stats">
|
|
<div class="listOne skeleton-card" v-for="i in 6" :key="i">
|
|
<el-skeleton animated style="width:100%;display:flex;align-items:center;padding:0 16px 0 20px">
|
|
<template #template>
|
|
<el-skeleton-item variant="circle" style="width:60px;height:60px;flex-shrink:0" />
|
|
<div style="margin-left:16px;flex:1">
|
|
<el-skeleton-item variant="h3" style="width:55%;margin-bottom:10px" />
|
|
<el-skeleton-item variant="text" style="width:35%" />
|
|
</div>
|
|
</template>
|
|
</el-skeleton>
|
|
</div>
|
|
</div>
|
|
<div class="list" v-else>
|
|
<div class="listOne" v-for="(item, index) in data.listData" :key="index">
|
|
<div class="left">
|
|
<img :src="item.url" alt="">
|
|
</div>
|
|
<div class="right">
|
|
<p>{{ item.num }}</p>
|
|
<p>{{ item.name }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 年级均分 & 等级占比 -->
|
|
<div class="cont1">
|
|
<div class="cont1Left">
|
|
<div class="title">
|
|
<p>年级体测平均成绩</p>
|
|
</div>
|
|
<div class="chart-skeleton" v-if="loading.stats">
|
|
<el-skeleton animated>
|
|
<template #template>
|
|
<div class="skeleton-chart-wrap">
|
|
<div class="skeleton-bars">
|
|
<div class="skeleton-bar-group" v-for="i in 7" :key="i">
|
|
<el-skeleton-item variant="rect"
|
|
:style="`width:24px;height:${40 + Math.random() * 120}px`" />
|
|
<el-skeleton-item variant="text" style="width:32px;margin-top:8px" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</el-skeleton>
|
|
</div>
|
|
<div id="barChart" v-show="!loading.stats"></div>
|
|
</div>
|
|
<div class="cont1Right">
|
|
<div class="title">
|
|
<div class="head">
|
|
<div class="left">
|
|
<p>各体测项目等级占比</p>
|
|
</div>
|
|
<div class="right">
|
|
<span>项目:</span>
|
|
<el-select @change="changeProject" v-model="data.project" placeholder="请选择"
|
|
style="width: 200px">
|
|
<el-option v-for="item in data.projectData" :key="item.categoryId"
|
|
:label="item.categoryName" :value="item.categoryId" />
|
|
</el-select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="chart-skeleton" v-if="loading.level">
|
|
<el-skeleton animated>
|
|
<template #template>
|
|
<div class="skeleton-chart-wrap">
|
|
<div class="skeleton-pie-wrap">
|
|
<el-skeleton-item variant="circle" style="width:130px;height:130px" />
|
|
<div class="skeleton-pie-legend">
|
|
<div class="skeleton-legend-item" v-for="i in 4" :key="i">
|
|
<el-skeleton-item variant="circle" style="width:10px;height:10px;flex-shrink:0" />
|
|
<el-skeleton-item variant="text" style="width:80px" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</el-skeleton>
|
|
</div>
|
|
<div id="levelbarChart" v-show="!loading.level"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 班级达标率 -->
|
|
<div class="cont3">
|
|
<div class="title">
|
|
<p>班级体测达标率对比</p>
|
|
</div>
|
|
<div class="chart-skeleton" v-if="loading.stats">
|
|
<el-skeleton animated>
|
|
<template #template>
|
|
<div class="skeleton-chart-wrap">
|
|
<div class="skeleton-bars">
|
|
<div class="skeleton-bar-group" v-for="i in 8" :key="i">
|
|
<div style="display:flex;gap:4px;align-items:flex-end">
|
|
<el-skeleton-item variant="rect"
|
|
:style="`width:14px;height:${50 + Math.random() * 100}px`" />
|
|
<el-skeleton-item variant="rect"
|
|
:style="`width:14px;height:${50 + Math.random() * 100}px`" />
|
|
<el-skeleton-item variant="rect"
|
|
:style="`width:14px;height:${50 + Math.random() * 100}px`" />
|
|
</div>
|
|
<el-skeleton-item variant="text" style="width:42px;margin-top:8px" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</el-skeleton>
|
|
</div>
|
|
<div class="cont3Chart" id="classBarChart" v-show="!loading.stats"></div>
|
|
</div>
|
|
|
|
<!-- 成绩趋势 -->
|
|
<div class="cont4">
|
|
<div class="title">
|
|
<div class="left">
|
|
<p>成绩趋势</p>
|
|
</div>
|
|
<div class="right">
|
|
<div class="one">
|
|
<p>项目:</p>
|
|
<el-select @change="changeTrendProject" v-model="data.trendProject" placeholder="请选择"
|
|
style="width: 240px">
|
|
<el-option v-for="item in data.projectData" :key="item.categoryId"
|
|
:label="item.categoryName" :value="item.categoryId" />
|
|
</el-select>
|
|
</div>
|
|
<div class="one">
|
|
<p>日期:</p>
|
|
<el-select @change="changeTrendDate" v-model="data.date" placeholder="请选择" style="width: 240px">
|
|
<el-option v-for="item in data.dateData" :key="item.value" :label="item.label"
|
|
:value="item.value" />
|
|
</el-select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="chart-skeleton" v-if="loading.trend">
|
|
<el-skeleton animated>
|
|
<template #template>
|
|
<div class="skeleton-chart-wrap skeleton-line-wrap">
|
|
<el-skeleton-item variant="rect" style="width:100%;height:3px;margin-bottom:24px" />
|
|
<div class="skeleton-line-dots">
|
|
<div class="skeleton-line-dot" v-for="i in 12" :key="i">
|
|
<el-skeleton-item variant="circle" style="width:8px;height:8px" />
|
|
<el-skeleton-item variant="text" style="width:28px;margin-top:8px" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</el-skeleton>
|
|
</div>
|
|
<div class="cont4Chart" id="trendChart" v-show="!loading.trend"></div>
|
|
</div>
|
|
|
|
<!-- 详细列表 -->
|
|
<div class="tableCont">
|
|
<p>详细列表</p>
|
|
<div v-if="loading.stats" class="skeleton-table">
|
|
<el-skeleton animated>
|
|
<template #template>
|
|
<div class="skeleton-table-header">
|
|
<el-skeleton-item variant="rect" v-for="i in 6" :key="i" style="height:16px;flex:1;margin:0 8px" />
|
|
</div>
|
|
<div class="skeleton-table-row" v-for="j in 8" :key="j">
|
|
<el-skeleton-item variant="text" v-for="i in 6" :key="i" style="flex:1;margin:0 8px" />
|
|
</div>
|
|
</template>
|
|
</el-skeleton>
|
|
</div>
|
|
<commonTable :tableNeed="data.tableNeed" @toView="toView" v-else-if="data.tableNeed.refresh"></commonTable>
|
|
</div>
|
|
</template><!-- end grade tab -->
|
|
|
|
<!-- 班级报告 Tab -->
|
|
<template v-else-if="activeTab === 'class'">
|
|
<div v-if="!classTabReady" class="emptyHint">
|
|
<el-icon><office-building /></el-icon>
|
|
<p>请在上方选择年级-班级,查看对应班级体测报告</p>
|
|
</div>
|
|
<ClassView1 v-else :key="classKey" class="embeddedReport" />
|
|
</template>
|
|
|
|
<!-- 教师报告 Tab -->
|
|
<template v-else-if="activeTab === 'teacher'">
|
|
<div v-if="!teacherTabReady" class="emptyHint">
|
|
<el-icon><user /></el-icon>
|
|
<p>请在上方选择教师,查看对应教师体测报告</p>
|
|
</div>
|
|
<TeacherView v-else :key="teacherKey" class="embeddedReport" />
|
|
</template>
|
|
|
|
<!-- 学生报告 Tab -->
|
|
<template v-else-if="activeTab === 'student'">
|
|
<div v-if="!studentTabReady" class="emptyHint">
|
|
<el-icon><avatar /></el-icon>
|
|
<p>请在上方依次选择班级和学生,查看对应学生体测报告</p>
|
|
</div>
|
|
<StudentDataView v-else :key="studentKey" class="embeddedReport" />
|
|
</template>
|
|
</div>
|
|
<commonDialog :list="data.dialogData" @cancle="confirm" @confirm="confirm">
|
|
<template #cont>
|
|
<div class="dialogManage">
|
|
<el-scrollbar height="400px">
|
|
<div class="cont">
|
|
<div class="one" @click="toViewStudentDetail(item)"
|
|
v-for="(item, index) in data.filterStudentList" :key="index">
|
|
<div class="left">
|
|
<img style="width: 100px;height: 100px;" :src="item.photo" alt="">
|
|
</div>
|
|
<div class="right">
|
|
<p>{{ item.studentName }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</el-scrollbar>
|
|
|
|
</div>
|
|
</template>
|
|
</commonDialog>
|
|
</template>
|
|
<script lang="ts" setup>
|
|
import { ref, onMounted, onBeforeMount, onBeforeUnmount, markRaw, nextTick } from 'vue';
|
|
import { ArrowDown, OfficeBuilding, User, Avatar, Loading } from '@element-plus/icons-vue'
|
|
import * as echarts from 'echarts';
|
|
import { useRoute } from 'vue-router';
|
|
import * as _api from '@/api/grade.js';
|
|
import commonTable from '@/views/motionData/trainingData/components/commonTbale/index.vue';
|
|
import eventBus from '@/eventBus.js';
|
|
import { ElMessage } from 'element-plus';
|
|
import commonDialog from '@/components/commonDialog/index.vue';
|
|
import ClassView1 from '@/views/classView1/index.vue';
|
|
import TeacherView from '@/views/teacherView/index.vue';
|
|
import StudentDataView from '@/views/StudentDataStatistics/index.vue';
|
|
const route = useRoute();
|
|
|
|
// ── Tab 切换状态 ──
|
|
const activeTab = ref('grade')
|
|
const gradeTabId = ref(null)
|
|
|
|
const classTabId = ref(null)
|
|
const classTabReady = ref(false)
|
|
const classKey = ref(0)
|
|
|
|
const teacherTabId = ref(null)
|
|
const teacherTabReady = ref(false)
|
|
const teacherKey = ref(0)
|
|
|
|
const studentClassTabId = ref(null)
|
|
const studentTabId = ref(null)
|
|
const studentTabReady = ref(false)
|
|
const studentKey = ref(0)
|
|
const studentList = ref([])
|
|
const studentListLoading = ref(false)
|
|
|
|
async function switchTab(tab: string) {
|
|
activeTab.value = tab
|
|
if (tab === 'grade') {
|
|
await nextTick()
|
|
if (!loading.value.stats) {
|
|
averageBar(data.value.averageBarData)
|
|
classBar(data.value.classBarData)
|
|
}
|
|
if (!loading.value.level) {
|
|
levelChart(data.value.levelChartData)
|
|
}
|
|
if (!loading.value.trend) {
|
|
trendEcharts(data.value.chartsData3)
|
|
}
|
|
}
|
|
}
|
|
|
|
async function onGradeTabChange(val: any) {
|
|
if (!val) return
|
|
data.value.newGradeId = val
|
|
data.value.gradeId = val
|
|
loading.value.stats = true
|
|
loading.value.level = true
|
|
loading.value.trend = true
|
|
data.value.tableNeed.refresh = false
|
|
await initCategory(val)
|
|
if (data.value.projectData.length > 0) {
|
|
data.value.project = data.value.projectData[0].categoryId
|
|
data.value.trendProject = data.value.projectData[0].categoryId
|
|
}
|
|
initGradeWholeDataStats(val)
|
|
initCategoryRankRatio(data.value.project, val)
|
|
initResultTrends(data.value.date, data.value.trendProject, val)
|
|
}
|
|
|
|
function onClassTabChange(val: any) {
|
|
if (!val || val.length < 2) return
|
|
const classId = val[1]
|
|
const gradeId = val[0]
|
|
localStorage.setItem('classId', JSON.stringify({ id: classId, gradeId: gradeId }))
|
|
classTabReady.value = true
|
|
classKey.value++
|
|
}
|
|
|
|
function onTeacherTabChange(val: any) {
|
|
if (!val) return
|
|
localStorage.setItem('teacherView', JSON.stringify({ id: val }))
|
|
teacherTabReady.value = true
|
|
teacherKey.value++
|
|
}
|
|
|
|
async function onStudentClassChange(val: any) {
|
|
studentTabReady.value = false
|
|
studentTabId.value = null
|
|
studentList.value = []
|
|
if (!val || val.length < 2) return
|
|
const classId = val[1]
|
|
studentListLoading.value = true
|
|
try {
|
|
const res = await _api.StudentGetClassAll(classId)
|
|
if (res.code == 200) {
|
|
studentList.value = res.data || []
|
|
}
|
|
} catch (e) {
|
|
console.error(e)
|
|
} finally {
|
|
studentListLoading.value = false
|
|
}
|
|
}
|
|
|
|
function onStudentTabChange(val: any) {
|
|
if (!val) return
|
|
const gradeId = studentClassTabId.value[0]
|
|
const classId = studentClassTabId.value[1]
|
|
const studentObj = {
|
|
...val,
|
|
studentNo: val.studentNo || val.StudentNo,
|
|
gradeId: gradeId,
|
|
classId: classId,
|
|
}
|
|
localStorage.setItem('studentNo', JSON.stringify(studentObj))
|
|
studentTabReady.value = true
|
|
studentKey.value++
|
|
}
|
|
|
|
const loading = ref({
|
|
stats: true, // 统计卡片 + 年级平均成绩图 + 班级达标率图 + 表格
|
|
level: true, // 各体测项目等级占比图
|
|
trend: true, // 成绩趋势图
|
|
})
|
|
const data = ref({
|
|
dialogData: {
|
|
dialogVisible: false,
|
|
width: 800,
|
|
title: '学生列表',
|
|
},
|
|
classes: [
|
|
|
|
],
|
|
ClassId: '',
|
|
gradeList: [],
|
|
gradeId: null,
|
|
teacherList: [],
|
|
teacherId: '',
|
|
studentId: '',
|
|
|
|
selectData: 1,
|
|
selectOptions: [{
|
|
label: '全部',
|
|
value: 1
|
|
}, {
|
|
label: '课堂模式',
|
|
value: 2
|
|
}, {
|
|
label: '自由模式',
|
|
value: 3
|
|
},],
|
|
listData: [{
|
|
url: require('@/assets/imgs/icon.png'),
|
|
num: 12,
|
|
name: '班级数量'
|
|
}, {
|
|
url: require('@/assets/imgs/icon.png'),
|
|
num: 12,
|
|
name: '学生数量'
|
|
}, {
|
|
url: require('@/assets/imgs/icon.png'),
|
|
num: '20%',
|
|
name: '优秀'
|
|
}, {
|
|
url: require('@/assets/imgs/icon.png'),
|
|
num: '20%',
|
|
name: '良好'
|
|
}, {
|
|
url: require('@/assets/imgs/icon.png'),
|
|
num: '20%',
|
|
name: '及格'
|
|
}, {
|
|
url: require('@/assets/imgs/icon.png'),
|
|
num: '20%',
|
|
name: '不及格'
|
|
}],
|
|
averageBarData: {
|
|
xData: [],
|
|
yData: [],
|
|
color: ['#4188FE']
|
|
},
|
|
project: 1,
|
|
projectData: [],
|
|
levelChartData: {
|
|
color: ['#4188FE', '#00DAAA', '#FFD11A', '#FF6C1A', '#7F70C9'],
|
|
seriesData: []
|
|
},
|
|
classBarData: {
|
|
xData: ['1班', '2班', '3班', '4班', '5班', '6班'],
|
|
yData: [],
|
|
yData1: [],
|
|
yData2: [],
|
|
color: ['#4188FE', '#00DAAA', '#FAC553'],
|
|
},
|
|
trendProject: 1,
|
|
date: 4,
|
|
dateData: [{
|
|
value: 1,
|
|
label: '近一周'
|
|
}, {
|
|
value: 2,
|
|
label: '近两周'
|
|
}, {
|
|
value: 3,
|
|
label: '近一月'
|
|
}, {
|
|
value: 4,
|
|
label: '近一年'
|
|
},],
|
|
chartsData3: {
|
|
ChartCont: {
|
|
xData: [],
|
|
yName: '分',
|
|
axisLabelColor: '#868A95',
|
|
legendColor: '#3F404A',
|
|
seriesName: "",
|
|
spitAreaColor: ['#fff', '#F6F7F9'],
|
|
labelColor: '#666566',
|
|
lineStyleColor: '#DDDEE2',
|
|
seriesData: [],
|
|
splitNumber: null,
|
|
color: '#0979FA',
|
|
areaStyle: {
|
|
color: {
|
|
type: "linear",
|
|
x: 0,
|
|
y: 0,
|
|
x2: 0,
|
|
y2: 1,
|
|
colorStops: [
|
|
{
|
|
offset: 0,
|
|
color: "#0979FA" // 0% 处的颜色
|
|
},
|
|
{
|
|
offset: 1,
|
|
color: "rgba(67,173,255,0.08)" // 100% 处的颜色
|
|
}
|
|
],
|
|
global: false // 缺省为 false
|
|
}
|
|
}
|
|
}
|
|
},
|
|
tableNeed: {
|
|
refresh: false,
|
|
showoperate: true,
|
|
showEdit: false,
|
|
showPagtion: 2,
|
|
pageIndex: 1,
|
|
pageSize: 10,
|
|
total: 0,
|
|
height: '66.5vh',
|
|
tdHeight: '56px',
|
|
tableLabel: [
|
|
{
|
|
label: "序号",
|
|
prop: "ind",
|
|
|
|
},
|
|
{
|
|
label: "年级-班级",
|
|
prop: "gradeAndClassName",
|
|
},
|
|
{
|
|
label: "人数",
|
|
prop: "count",
|
|
},
|
|
{
|
|
label: "教师姓名",
|
|
prop: "teacherName",
|
|
},
|
|
{
|
|
label: "授课次数",
|
|
prop: "accumulatedTeachingTimes",
|
|
},
|
|
{
|
|
label: "达标率",
|
|
prop: "passRate",
|
|
},
|
|
],
|
|
tableData: [],
|
|
},
|
|
// gradeId: null
|
|
newGradeId: null,
|
|
allStudentList: [],
|
|
filterStudentList: []
|
|
})
|
|
function confirm() {
|
|
data.value.dialogData.dialogVisible = false
|
|
}
|
|
onMounted(async () => {
|
|
data.value.newGradeId = route.params.id || JSON.parse(localStorage.getItem('gradeId'))?.id || 0;
|
|
data.value.gradeId = data.value.newGradeId
|
|
gradeTabId.value = data.value.newGradeId || null
|
|
// 先加载项目分类,确保 project 初始值是有效的 categoryId
|
|
await initCategory(data.value.newGradeId)
|
|
if (data.value.projectData.length > 0) {
|
|
data.value.project = data.value.projectData[0].categoryId
|
|
data.value.trendProject = data.value.projectData[0].categoryId
|
|
}
|
|
await nextTick()
|
|
initGradeWholeDataStats(data.value.newGradeId)
|
|
initCategoryRankRatio(data.value.project, data.value.newGradeId)
|
|
initResultTrends(data.value.date, data.value.trendProject, data.value.newGradeId)
|
|
|
|
GetGradeNames()
|
|
getAllGrade()
|
|
GetTeacherNames()
|
|
})
|
|
onBeforeUnmount(() => {
|
|
// localStorage.removeItem('gradeId')
|
|
})
|
|
function changeProject(e) {
|
|
console.log(e, 'changeProject')
|
|
data.value.project = e
|
|
data.value.levelChartData.seriesData = []
|
|
initCategoryRankRatio(data.value.project, data.value.gradeId)
|
|
}
|
|
function changeTrendProject(e) {
|
|
data.value.trendProject = e
|
|
data.value.chartsData3.ChartCont.xData = []
|
|
data.value.chartsData3.ChartCont.seriesData = []
|
|
initResultTrends(data.value.date, data.value.trendProject, data.value.gradeId)
|
|
}
|
|
function changeTrendDate(e) {
|
|
data.value.date = e
|
|
data.value.chartsData3.ChartCont.xData = []
|
|
data.value.chartsData3.ChartCont.seriesData = []
|
|
initResultTrends(data.value.date, data.value.trendProject, data.value.gradeId)
|
|
}
|
|
// 获取年级对应项目名称
|
|
async function initCategory(val) {
|
|
try {
|
|
const res = await _api.CategoryList(val != 0 ? val : 1);
|
|
if (res.code == 200) {
|
|
data.value.projectData = res.data
|
|
}
|
|
} catch (error) {
|
|
console.error('Error occurred while calling API:', error);
|
|
}
|
|
}
|
|
// 各体测项目等级占比
|
|
async function initCategoryRankRatio(val, val1) {
|
|
try {
|
|
const res = await _api.CategoryRankRatio(val, val1);
|
|
if (res.code == 200) {
|
|
data.value.levelChartData.seriesData = []
|
|
for (let i in res.data) {
|
|
data.value.levelChartData.seriesData.push({ value: (res.data[i] * 100).toFixed(0), name: i })
|
|
}
|
|
loading.value.level = false
|
|
await nextTick()
|
|
levelChart(data.value.levelChartData)
|
|
}
|
|
} catch (error) {
|
|
loading.value.level = false
|
|
console.error('Error occurred while calling API:', error);
|
|
}
|
|
}
|
|
// 成绩趋势
|
|
async function initResultTrends(val, val1, val2) {
|
|
try {
|
|
const res = await _api.ResultTrends(val, val1, val2);
|
|
if (res.code == 200) {
|
|
data.value.chartsData3.ChartCont.xData = res.data.axisX
|
|
data.value.chartsData3.ChartCont.seriesData = res.data.axisY
|
|
loading.value.trend = false
|
|
await nextTick()
|
|
trendEcharts(data.value.chartsData3)
|
|
}
|
|
} catch (error) {
|
|
loading.value.trend = false
|
|
console.error('Error occurred while calling API:', error);
|
|
}
|
|
}
|
|
// 年级数据统计
|
|
async function initGradeWholeDataStats(val) {
|
|
try {
|
|
const res = await _api.GradeWholeDataStats(val);
|
|
if (res.code == 200) {
|
|
data.value.listData[0].num = res.data.classCount
|
|
data.value.listData[1].num = res.data.studentCount
|
|
data.value.listData[2].num = res.data.excellentRate + '%'
|
|
data.value.listData[3].num = res.data.fineRate + '%'
|
|
data.value.listData[4].num = res.data.passRate + '%'
|
|
data.value.listData[5].num = res.data.failRate + '%'
|
|
data.value.averageBarData.xData = []
|
|
data.value.averageBarData.yData = []
|
|
for (let i in res.data.testResultAvg) {
|
|
data.value.averageBarData.xData.push(i)
|
|
data.value.averageBarData.yData.push(res.data.testResultAvg[i].toFixed(0))
|
|
}
|
|
data.value.classBarData.xData = res.data.testResultPassRate.axisX
|
|
data.value.classBarData.yData = []
|
|
data.value.classBarData.yData1 = []
|
|
data.value.classBarData.yData2 = []
|
|
res.data.testResultPassRate.axisY.map((i) => {
|
|
data.value.classBarData.yData.push((i.maleValue * 100).toFixed(0))
|
|
data.value.classBarData.yData1.push((i.femaleValue * 100).toFixed(0))
|
|
data.value.classBarData.yData2.push((i.overallValue * 100).toFixed(0))
|
|
})
|
|
data.value.tableNeed.tableData = res.data.gradeDetailsList
|
|
data.value.tableNeed.tableData.map((i, ind) => {
|
|
i.ind = ind + 1
|
|
i.passRate = (i.passRate * 100).toFixed(0) + '%'
|
|
})
|
|
data.value.tableNeed.refresh = true
|
|
loading.value.stats = false
|
|
await nextTick()
|
|
averageBar(data.value.averageBarData)
|
|
classBar(data.value.classBarData)
|
|
|
|
}
|
|
} catch (error) {
|
|
loading.value.stats = false
|
|
console.error('Error occurred while calling API:', error);
|
|
}
|
|
}
|
|
// 查看详情
|
|
function toView(val) {
|
|
console.log(val, 'valllllltoView==============>')
|
|
const gradeName = val.gradeAndClassName.split("-")[0];
|
|
console.log(gradeName,'gradeName')
|
|
console.log(data.value.classes,'data.value.classes')
|
|
const grade = data.value.classes.find(item => item.label.startsWith(gradeName));
|
|
console.log(grade,'gradeeeeee')
|
|
let params = {
|
|
id: val.classId,
|
|
gradeId: grade.value
|
|
}
|
|
// let params=[val.classId,data.value.newGradeId!=0?data.value.newGradeId:1]
|
|
eventBus.emit('toDetail', params)
|
|
}
|
|
// 年级体测平均成绩
|
|
function averageBar(val) {
|
|
echarts.dispose(document.getElementById('barChart'));
|
|
let myChart = markRaw(echarts.init(document.getElementById('barChart')));
|
|
myChart.setOption(
|
|
{
|
|
color: val.color,
|
|
legend: {
|
|
icon: "rect",
|
|
x: "right",
|
|
padding: [5, 40, 0, 0],
|
|
itemWidth: 12,
|
|
itemHeight: 6,
|
|
itemGap: 41,
|
|
textStyle: {
|
|
color: "#000",
|
|
fontSize: "12px"
|
|
}
|
|
},
|
|
grid: {
|
|
left: "2%",
|
|
right: "2%",
|
|
// top:'10%',
|
|
bottom: "5%",
|
|
containLabel: true
|
|
},
|
|
xAxis: {
|
|
type: 'category',
|
|
data: val.xData,
|
|
axisTick: {
|
|
show: false
|
|
},
|
|
axisLabel: {
|
|
//x轴文字的配置
|
|
show: true,
|
|
color: '#868A95',
|
|
fontSize: 12
|
|
},
|
|
axisLine: {
|
|
show: true,
|
|
lineStyle: {
|
|
color: '#DDDEE2'
|
|
}
|
|
},
|
|
},
|
|
yAxis: {
|
|
type: 'value',
|
|
name: '分',
|
|
nameTextStyle: {
|
|
color: "#868A95",
|
|
fontSize: 12,
|
|
// padding: val.Ypadding
|
|
},
|
|
// interval: 20,
|
|
axisLabel: {
|
|
color: '#868A95',
|
|
fontStyle: "normal",
|
|
fontWeight: "400",
|
|
fontSize: "12"
|
|
},
|
|
axisLine: {
|
|
show: true,
|
|
lineStyle: {
|
|
color: '#DDDEE2'
|
|
}
|
|
},
|
|
splitLine: {
|
|
//分割线配置
|
|
show: false,
|
|
},
|
|
splitArea: {
|
|
show: true,
|
|
areaStyle: {
|
|
// color: val.splitAreaColor,
|
|
opacity: 1
|
|
}
|
|
}
|
|
|
|
},
|
|
series: [
|
|
{
|
|
name: '平均成绩',
|
|
data: val.yData,
|
|
type: 'bar',
|
|
barWidth: 20,
|
|
itemStyle: {
|
|
normal: {
|
|
label: {
|
|
formatter: "{c}",
|
|
show: true,
|
|
position: "top",
|
|
textStyle: {
|
|
fontWeight: "400",
|
|
fontSize: "14",
|
|
color: "#000"
|
|
}
|
|
},
|
|
}
|
|
}
|
|
},
|
|
]
|
|
}
|
|
)
|
|
window.onresize = function () { // 自适应大小
|
|
myChart.resize();
|
|
};
|
|
}
|
|
// 各体测项目等级占比
|
|
function levelChart(val) {
|
|
echarts.dispose(document.getElementById('levelbarChart'));
|
|
let myChart = markRaw(echarts.init(document.getElementById('levelbarChart')));
|
|
// 绘制图表
|
|
myChart.setOption({
|
|
color: val.color,
|
|
series: [
|
|
{
|
|
type: 'pie',
|
|
radius: ['50%', '70%'],
|
|
center: ['50%', '45%'],
|
|
startAngle: 0,
|
|
hoverAnimation: true,
|
|
avoidLabelOverlap: true,
|
|
animationDurationUpdate: 2000, // 更新动画时长为 1s
|
|
animationEasingUpdate: 'cubicInOut', // 更新动画缓动效果为 'quinticInOut'
|
|
silent: true,
|
|
label: {
|
|
color: '#000',
|
|
show: true,
|
|
position: 'outside',
|
|
formatter: function (value) {
|
|
console.log(value)
|
|
let str = '{a|' + value.name + ':}' + '{a|' + value.value + '%}' + '\n\n'
|
|
return str
|
|
},
|
|
rich: {
|
|
a: {
|
|
padding: [10, 0, 0, 0],
|
|
},
|
|
},
|
|
emphasis: {
|
|
show: true,
|
|
textStyle: {
|
|
fontSize: 12,
|
|
fontWeight: 'bold'
|
|
}
|
|
}
|
|
},
|
|
data: val.seriesData,
|
|
emphasis: {
|
|
itemStyle: {
|
|
shadowBlur: 10,
|
|
shadowOffsetX: 0,
|
|
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
|
}
|
|
}
|
|
}
|
|
]
|
|
});
|
|
window.onresize = function () { // 自适应大小
|
|
myChart.resize();
|
|
};
|
|
}
|
|
// 班级体测达标率对比
|
|
function classBar(val) {
|
|
echarts.dispose(document.getElementById('classBarChart'));
|
|
let myChart = markRaw(echarts.init(document.getElementById('classBarChart')));
|
|
myChart.setOption(
|
|
{
|
|
color: val.color,
|
|
tooltip: {},
|
|
legend: {
|
|
icon: "rect",
|
|
x: "right",
|
|
padding: [5, 40, 0, 0],
|
|
itemWidth: 12,
|
|
itemHeight: 6,
|
|
itemGap: 41,
|
|
textStyle: {
|
|
color: "#000",
|
|
fontSize: "12px"
|
|
}
|
|
},
|
|
grid: {
|
|
left: "2%",
|
|
right: "2%",
|
|
// top:'10%',
|
|
bottom: "5%",
|
|
containLabel: true
|
|
},
|
|
xAxis: {
|
|
type: 'category',
|
|
data: val.xData,
|
|
axisTick: {
|
|
show: false
|
|
},
|
|
axisLabel: {
|
|
//x轴文字的配置
|
|
show: true,
|
|
color: '#868A95',
|
|
fontSize: 12
|
|
},
|
|
axisLine: {
|
|
show: true,
|
|
lineStyle: {
|
|
color: '#DDDEE2'
|
|
}
|
|
},
|
|
},
|
|
yAxis: {
|
|
type: 'value',
|
|
name: '%',
|
|
nameTextStyle: {
|
|
color: "#868A95",
|
|
fontSize: 12,
|
|
// padding: val.Ypadding
|
|
},
|
|
// interval: 20,
|
|
axisLabel: {
|
|
color: '#868A95',
|
|
fontStyle: "normal",
|
|
fontWeight: "400",
|
|
fontSize: "12"
|
|
},
|
|
axisLine: {
|
|
show: true,
|
|
lineStyle: {
|
|
color: '#DDDEE2'
|
|
}
|
|
},
|
|
splitLine: {
|
|
//分割线配置
|
|
show: false,
|
|
},
|
|
splitArea: {
|
|
show: true,
|
|
areaStyle: {
|
|
// color: val.splitAreaColor,
|
|
opacity: 1
|
|
}
|
|
}
|
|
|
|
},
|
|
series: [
|
|
{
|
|
name: '男生',
|
|
data: val.yData,
|
|
type: 'bar',
|
|
barWidth: null, // 宽度自适应
|
|
barMaxWidth: 20, // 限制最大宽度为 20
|
|
// itemStyle: {
|
|
// normal: {
|
|
// label: {
|
|
// formatter: "{c}%",
|
|
// show: true,
|
|
// position: "top",
|
|
// textStyle: {
|
|
// fontWeight: "400",
|
|
// fontSize: "10",
|
|
// color: "#666566"
|
|
// }
|
|
// },
|
|
// // color: function (params) {
|
|
// // let colorList = val.color;
|
|
// // return colorList[params.dataIndex];
|
|
// // }
|
|
// }
|
|
// }
|
|
},
|
|
{
|
|
data: val.yData1,
|
|
name: '女生',
|
|
type: 'bar',
|
|
barWidth: null, // 宽度自适应
|
|
barMaxWidth: 20, // 限制最大宽度为 20
|
|
// itemStyle: {
|
|
// normal: {
|
|
// label: {
|
|
// formatter: "{c}%",
|
|
// show: true,
|
|
// position: "top",
|
|
// textStyle: {
|
|
// fontWeight: "400",
|
|
// fontSize: "10",
|
|
// color: "#666566"
|
|
// }
|
|
// },
|
|
// // color: function (params) {
|
|
// // let colorList = val.color;
|
|
// // return colorList[params.dataIndex];
|
|
// // }
|
|
// }
|
|
// }
|
|
},
|
|
{
|
|
data: val.yData2,
|
|
name: '总体',
|
|
type: 'bar',
|
|
barWidth: null, // 宽度自适应
|
|
barMaxWidth: 20, // 限制最大宽度为 20
|
|
// itemStyle: {
|
|
// normal: {
|
|
// label: {
|
|
// formatter: "{c}%",
|
|
// show: true,
|
|
// position: "insideTopLeft",
|
|
// textStyle: {
|
|
// fontWeight: "400",
|
|
// fontSize: "10",
|
|
// color: "#666566"
|
|
// }
|
|
// },
|
|
// // color: function (params) {
|
|
// // let colorList = val.color;
|
|
// // return colorList[params.dataIndex];
|
|
// // }
|
|
// }
|
|
// }
|
|
}
|
|
]
|
|
}
|
|
)
|
|
window.onresize = function () { // 自适应大小
|
|
myChart.resize();
|
|
};
|
|
}
|
|
// 成绩趋势
|
|
function trendEcharts(val) {
|
|
echarts.dispose(document.getElementById('trendChart'));
|
|
let myChart = markRaw(echarts.init(document.getElementById('trendChart')));
|
|
myChart.setOption(
|
|
{
|
|
grid: {
|
|
left: "2%",
|
|
right: "2%",
|
|
bottom: "10%",
|
|
containLabel: true
|
|
},
|
|
color: [val.ChartCont.color],
|
|
tooltip: {
|
|
trigger: "axis",
|
|
formatter: function (params) {
|
|
|
|
// 自定义tooltip内容
|
|
return `${params[0].name}<br/>平均成绩:${params[0].data}`;
|
|
}
|
|
},
|
|
legend: {
|
|
show: val.showLegend ? false : true,
|
|
icon: "rect",
|
|
x: "right",
|
|
padding: [val.padTop ? val.padTop : 35, val.padRight ? val.padRight : 20, 0, 0],
|
|
itemWidth: val.ChartCont.legendWidth ? val.ChartCont.legendWidth : 19,
|
|
itemHeight: val.ChartCont.legendHeight ? val.ChartCont.legendHeight : 9,
|
|
itemGap: 41,
|
|
textStyle: {
|
|
color: val.ChartCont.legendColor ? val.ChartCont.legendColor : '#F2F2F2',
|
|
fontSize: "12px"
|
|
}
|
|
},
|
|
toolbox: {
|
|
show: false
|
|
},
|
|
xAxis: {
|
|
type: val.ChartCont.xType ? val.ChartCont.xType : "category",
|
|
// position: "bottom",
|
|
boundaryGap: false,
|
|
min: val.ChartCont.min ? val.ChartCont.min : null,
|
|
max: val.ChartCont.max ? val.ChartCont.max : null,
|
|
splitNumber: val.ChartCont.splitNumber ? val.ChartCont.splitNumber : null,
|
|
data: val.ChartCont.xData,
|
|
axisTick: {
|
|
show: false
|
|
},
|
|
axisLabel: {
|
|
//x轴文字的配置
|
|
show: true,
|
|
// textStyle: {
|
|
color: val.ChartCont.axisLabelColor ? val.ChartCont.axisLabelColor : '#fff',
|
|
fontSize: 12
|
|
// }
|
|
},
|
|
axisLine: {
|
|
show: true,
|
|
lineStyle: {
|
|
color: val.ChartCont.lineStyleColor ? val.ChartCont.lineStyleColor : "#1C3B36"
|
|
}
|
|
}
|
|
},
|
|
yAxis: {
|
|
type: "value",
|
|
name: val.ChartCont.yName,
|
|
nameTextStyle: {
|
|
color: val.ChartCont.axisLabelColor ? val.ChartCont.axisLabelColor : '#fff',
|
|
fontSize: 12,
|
|
padding: [0, val.chartsPadding ? val.chartsPadding : 20, 0, 0]
|
|
},
|
|
axisLabel: {
|
|
color: val.ChartCont.axisLabelColor ? val.ChartCont.axisLabelColor : '#fff',
|
|
fontStyle: "normal",
|
|
fontWeight: "400",
|
|
fontSize: "12",
|
|
align: val.ChartCont.yAxisAlign ? val.ChartCont.yAxisAlign : null,
|
|
margin: val.ChartCont.yAxisMargin ? val.ChartCont.yAxisMargin : 10, // 可以调整偏移量
|
|
},
|
|
axisLine: {
|
|
show: true,
|
|
lineStyle: {
|
|
color: val.ChartCont.lineStyleColor ? val.ChartCont.lineStyleColor : "#1C3B36"
|
|
}
|
|
},
|
|
splitLine: {
|
|
//分割线配置
|
|
show: false,
|
|
lineStyle: {
|
|
color: "#012624"
|
|
}
|
|
},
|
|
splitArea: {
|
|
show: true,
|
|
areaStyle: {
|
|
color: val.ChartCont.spitAreaColor ? val.ChartCont.spitAreaColor : ["rgba(200,200,200,0.3)", "#8AFFDD"],
|
|
opacity: val.ChartCont.spitAreaColor ? 1 : 0.04
|
|
}
|
|
}
|
|
},
|
|
series: [
|
|
{
|
|
name: val.ChartCont.seriesName1,
|
|
// stack: 'Total',
|
|
symbol: 'circle', //将小圆点改成实心 不写symbol默认空心
|
|
symbolSize: 5,
|
|
type: "line",
|
|
// smooth: true,
|
|
itemStyle: {
|
|
normal: {
|
|
label: {
|
|
show: true,
|
|
fontSize: 14,
|
|
fontFamily: 'DIN-Black',
|
|
fontWeight: 900,
|
|
color: val.ChartCont.labelColor ? val.ChartCont.labelColor : '#fff',
|
|
position: 'top',
|
|
}
|
|
}
|
|
},
|
|
// symbol: 'circle',
|
|
data: val.ChartCont.seriesData,
|
|
// lineStyle: {
|
|
// width: val.ChartCont.seriesData.length === 1 ? 0 : 2, // 如果只有一条数据,隐藏线条
|
|
// },
|
|
areaStyle: val.ChartCont.areaStyle,
|
|
},
|
|
],
|
|
visualMap: val.ChartCont.visualMap ? val.ChartCont.visualMap : null
|
|
}
|
|
)
|
|
}
|
|
|
|
function transformData(data) {
|
|
return data.map(grade => ({
|
|
value: grade.id,
|
|
label: grade.gradeName,
|
|
children: grade.class.map(cls => ({
|
|
value: cls.id,
|
|
label: cls.className
|
|
}))
|
|
}));
|
|
}
|
|
//年级接口
|
|
async function GetGradeNames() {
|
|
try {
|
|
const res = await _api.GetGradeName();
|
|
if (res.code == 200) {
|
|
if (res.data) {
|
|
data.value.classes = transformData(res.data)
|
|
}
|
|
} else {
|
|
ElMessage.error(res.message)
|
|
}
|
|
} catch (error) {
|
|
console.error('Error occurred while calling API:', error);
|
|
}
|
|
}
|
|
//班级接口
|
|
async function GetClassNames(val, num) {
|
|
try {
|
|
const res = await _api.GetClassNames(val);
|
|
if (res.code == 200) {
|
|
if (res.data) {
|
|
data.value.classes = res.data
|
|
}
|
|
} else {
|
|
ElMessage.error(res.message)
|
|
}
|
|
} catch (error) {
|
|
console.error('Error occurred while calling API:', error);
|
|
}
|
|
}
|
|
|
|
//获取所有年级名称
|
|
async function getAllGrade() {
|
|
try {
|
|
const res = await _api.GetGradeName();
|
|
if (res.code == 200) {
|
|
data.value.gradeList = res.data;
|
|
console.log(data.value.gradeList, 'data.value.gradeList')
|
|
}
|
|
} catch (error) {
|
|
console.error('Error occurred while calling API:', error);
|
|
}
|
|
}
|
|
//获取所有老师名称
|
|
async function GetTeacherNames() {
|
|
try {
|
|
const res = await _api.GetTeacherNames();
|
|
if (res.code == 200) {
|
|
data.value.teacherList = res.data;
|
|
}
|
|
} catch (error) {
|
|
console.error('Error occurred while calling API:', error);
|
|
}
|
|
}
|
|
|
|
//获取所有学生
|
|
async function initStudent() {
|
|
try {
|
|
const res = await _api.StudentGetClassPageList('', '', '', '', '', 1, 1000000);
|
|
if (res.code == 200) {
|
|
console.log(res, 'initStudent')
|
|
data.value.allStudentList = res.data.datas
|
|
|
|
}
|
|
} catch (error) {
|
|
console.error('Error occurred while calling API:', error);
|
|
}
|
|
}
|
|
function toViewGrade(val) {
|
|
console.log(val, 'valllll')
|
|
// return
|
|
let params = {
|
|
id: val
|
|
}
|
|
data.value.newGradeId = val
|
|
eventBus.emit('toViewGrade', params);
|
|
}
|
|
function toStudentView(val) {
|
|
console.log(val, 'vallllltoStudentView')
|
|
let params = {
|
|
id: val[1],
|
|
gradeId: val[0]
|
|
}
|
|
console.log(params, 'paramstoStudentView')
|
|
eventBus.emit('toDetail', params);
|
|
}
|
|
function toView1(val) {
|
|
console.log(val, 'valllll')
|
|
let params = {
|
|
id: val
|
|
}
|
|
eventBus.emit('toView1', params);
|
|
}
|
|
async function toViewStudent(val) {
|
|
console.log(val, 'valllll')
|
|
await initStudent()
|
|
data.value.filterStudentList = data.value.allStudentList.filter(item => item.classId == val[1])
|
|
console.log(data.value.filterStudentList, 'kkkkkkk')
|
|
|
|
data.value.dialogData.dialogVisible = true
|
|
}
|
|
function toViewStudentDetail(val) {
|
|
console.log(val, 'toViewStudentDetail')
|
|
let k = data.value.gradeList.filter(item => item.gradeName == val.gradeName)
|
|
val.gradeId = k[0].id
|
|
eventBus.emit('toViewStudent', val);
|
|
}
|
|
</script>
|
|
|
|
<style lang="less" scoped>
|
|
.dialogManage {
|
|
.cont {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
justify-content: space-between;
|
|
|
|
.one {
|
|
width: 150px;
|
|
height: 150px;
|
|
|
|
img {
|
|
display: block;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
p {
|
|
font-family: PingFangSC;
|
|
font-weight: 400;
|
|
font-size: 14px;
|
|
color: rgba(0, 0, 0, 0.63);
|
|
text-align: center;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.workOrder {
|
|
width: calc(100% - 40px);
|
|
height: auto;
|
|
margin: 20px;
|
|
|
|
.ipt {
|
|
width: 100%;
|
|
min-height: 72px;
|
|
height: auto;
|
|
padding: 16px 0;
|
|
display: flex;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
gap: 12px;
|
|
background: #FFFFFF;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
|
|
|
.one {
|
|
display: flex;
|
|
align-items: center;
|
|
margin-right: 0;
|
|
margin-left: 20px;
|
|
|
|
p {
|
|
font-family: PingFangSC;
|
|
font-weight: 400;
|
|
font-size: 14px;
|
|
color: rgba(0, 0, 0, 0.85);
|
|
margin-right: 10px;
|
|
}
|
|
}
|
|
}
|
|
|
|
.head {
|
|
width: 100%;
|
|
height: 40px;
|
|
display: flex;
|
|
align-items: center;
|
|
margin-left: 5px;
|
|
|
|
.one {
|
|
width: 330px;
|
|
height: 40px;
|
|
display: flex;
|
|
align-items: center;
|
|
background: #FFFFFF;
|
|
border-radius: 8px;
|
|
// margin-left: 20px;
|
|
margin-right: 20px;
|
|
|
|
p {
|
|
width: 84px;
|
|
height: 40px;
|
|
border: 1px solid rgba(0, 0, 0, 0.15);
|
|
text-align: center;
|
|
line-height: 40px;
|
|
font-family: PingFangSC;
|
|
font-weight: 400;
|
|
font-size: 14px;
|
|
color: rgba(0, 0, 0, 0.85);
|
|
border-right: 0px;
|
|
border-radius: 4px 0px 0px 4px;
|
|
}
|
|
|
|
.m-2 {
|
|
width: 246px;
|
|
|
|
// height: 60px;
|
|
:deep(.el-input__wrapper) {
|
|
height: 40px;
|
|
border-radius: 0 4px 4px 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.list {
|
|
width: 100%;
|
|
height: auto;
|
|
min-height: 100px;
|
|
display: flex;
|
|
align-items: stretch;
|
|
flex-wrap: wrap;
|
|
gap: 16px;
|
|
margin-top: 16px;
|
|
|
|
.listOne {
|
|
flex: 1;
|
|
min-width: 180px;
|
|
height: 100px;
|
|
background: #FFFFFF;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
|
display: flex;
|
|
align-items: center;
|
|
|
|
.left {
|
|
width: 60px;
|
|
height: 60px;
|
|
background: linear-gradient(135deg, rgba(65, 136, 254, 0.2), rgba(65, 136, 254, 0.08));
|
|
border-radius: 12px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
margin: 20px 20px;
|
|
flex-shrink: 0;
|
|
|
|
img {
|
|
width: 36px;
|
|
height: 36px;
|
|
}
|
|
}
|
|
|
|
.right {
|
|
p:nth-of-type(1) {
|
|
height: auto;
|
|
font-family: DINAlternate;
|
|
font-weight: 900;
|
|
font-size: 22px;
|
|
color: #4188FE;
|
|
}
|
|
|
|
p:nth-of-type(2) {
|
|
font-family: PingFangSC;
|
|
font-weight: 400;
|
|
font-size: 14px;
|
|
color: rgba(0, 0, 0, 0.85);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.cont1 {
|
|
width: 100%;
|
|
height: auto;
|
|
min-height: 330px;
|
|
display: flex;
|
|
align-items: stretch;
|
|
gap: 16px;
|
|
margin-top: 16px;
|
|
|
|
.cont1Left,
|
|
.cont1Right {
|
|
flex: 1;
|
|
min-width: 0;
|
|
height: auto;
|
|
background: #FFFFFF;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
|
padding-bottom: 16px;
|
|
|
|
.title {
|
|
width: 100%;
|
|
height: auto;
|
|
min-height: 48px;
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 0 20px;
|
|
border-bottom: 1px solid #F5F5F5;
|
|
margin-bottom: 4px;
|
|
|
|
p {
|
|
font-family: PingFangSC;
|
|
font-weight: 600;
|
|
font-size: 16px;
|
|
color: rgba(0, 0, 0, 0.85);
|
|
}
|
|
}
|
|
|
|
#barChart {
|
|
width: 100%;
|
|
height: 260px;
|
|
}
|
|
|
|
#levelbarChart {
|
|
width: 100%;
|
|
height: 260px;
|
|
}
|
|
|
|
// .contList {
|
|
// width: 546px;
|
|
// height: 100%;
|
|
// display: flex;
|
|
// flex-wrap: wrap;
|
|
|
|
// .descript {
|
|
// width: 235px;
|
|
// height: 75px;
|
|
// background: rgba(0, 86, 221, 0.25);
|
|
// border-radius: 8px;
|
|
// margin-top: 20px;
|
|
// display: flex;
|
|
// flex-direction: column;
|
|
// align-items: center;
|
|
// justify-content: center;
|
|
// margin-right: 25px;
|
|
|
|
// p:nth-of-type(1) {
|
|
// height: 0px;
|
|
// font-family: DINAlternate;
|
|
// font-weight: 900;
|
|
// font-size: 24px;
|
|
// color: #4188FE;
|
|
// margin-top: 8px;
|
|
// }
|
|
|
|
// p:nth-of-type(2) {
|
|
// font-family: PingFangSC;
|
|
// font-weight: 400;
|
|
// font-size: 14px;
|
|
// color: #666566;
|
|
// }
|
|
// }
|
|
|
|
// .descript:nth-of-type(3),
|
|
// .descript:nth-of-type(4) {
|
|
// margin-top: -5px;
|
|
// }
|
|
// }
|
|
}
|
|
|
|
.cont1Right {
|
|
.title {
|
|
.head {
|
|
width: 100%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
|
|
.left {
|
|
p {
|
|
font-family: PingFangSC;
|
|
font-weight: 600;
|
|
font-size: 16px;
|
|
color: rgba(0, 0, 0, 0.85);
|
|
}
|
|
}
|
|
|
|
.right {
|
|
display: flex;
|
|
align-items: center;
|
|
|
|
span {
|
|
display: inline-block;
|
|
font-family: PingFangSC;
|
|
font-weight: 400;
|
|
font-size: 14px;
|
|
color: rgba(0, 0, 0, 0.65);
|
|
margin-right: 8px;
|
|
white-space: nowrap;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.cont2 {
|
|
width: 100%;
|
|
height: 350px;
|
|
display: flex;
|
|
margin-top: 20px;
|
|
margin-left: 5px;
|
|
|
|
.cont2Left {
|
|
width: 820px;
|
|
height: 350px;
|
|
background: #FFFFFF;
|
|
border-radius: 8px;
|
|
margin-right: 20px;
|
|
|
|
.title {
|
|
width: 100%;
|
|
height: 60px;
|
|
display: flex;
|
|
align-items: center;
|
|
|
|
p {
|
|
width: calc(100% - 240px);
|
|
font-family: PingFangSC;
|
|
font-weight: 600;
|
|
font-size: 16px;
|
|
color: rgba(0, 0, 0, 0.85);
|
|
margin-left: 20px;
|
|
}
|
|
|
|
.tabList {
|
|
width: 240px;
|
|
height: 30px;
|
|
display: flex;
|
|
align-items: center;
|
|
|
|
p {
|
|
width: 83px;
|
|
height: 24px;
|
|
background: #FAFAFA;
|
|
// background: #4188FE;
|
|
border-radius: 12px;
|
|
border: 1px solid rgba(0, 0, 0, 0.15);
|
|
text-align: center;
|
|
line-height: 24px;
|
|
font-size: 12px;
|
|
font-family: PingFangSC;
|
|
font-weight: 400;
|
|
color: rgba(120, 118, 118, 0.85);
|
|
margin-left: 20px;
|
|
margin-top: 20px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.active {
|
|
background: #4188FE;
|
|
color: rgba(255, 255, 255, 0.85);
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
.centerOneCont {
|
|
width: 100%;
|
|
height: 25px;
|
|
display: flex;
|
|
align-items: center;
|
|
margin-left: 20px;
|
|
margin-top: 30px;
|
|
|
|
.left {
|
|
|
|
.average {
|
|
width: 300px;
|
|
height: 300px;
|
|
margin-top: 180px;
|
|
}
|
|
}
|
|
|
|
.right {
|
|
width: 410px;
|
|
margin-top: 180px;
|
|
|
|
.one {
|
|
width: 100%;
|
|
height: 50px;
|
|
margin-bottom: 20px;
|
|
margin-left: 20px;
|
|
position: relative;
|
|
|
|
.head {
|
|
width: 100%;
|
|
height: 30px;
|
|
display: flex;
|
|
align-items: center;
|
|
// justify-content: space-between;
|
|
|
|
p,
|
|
h5 {
|
|
font-family: PingFangSC;
|
|
font-weight: 500;
|
|
font-size: 14px;
|
|
color: #2C2C2C;
|
|
}
|
|
|
|
h5 {
|
|
margin-left: 10px;
|
|
}
|
|
|
|
p::before {
|
|
content: ' ';
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
position: absolute;
|
|
top: 21%;
|
|
left: 0px;
|
|
}
|
|
|
|
|
|
|
|
p {
|
|
margin-left: 13px;
|
|
}
|
|
}
|
|
|
|
.bodyCont {
|
|
width: calc(100% - 60px);
|
|
height: 25px;
|
|
display: flex;
|
|
margin-top: -10px;
|
|
|
|
p {
|
|
font-family: PingFangSC;
|
|
font-weight: 500;
|
|
font-size: 12px;
|
|
color: #868A95;
|
|
margin-right: 27px;
|
|
}
|
|
}
|
|
|
|
.chart {
|
|
width: 410px;
|
|
height: 100%;
|
|
}
|
|
}
|
|
|
|
.one:nth-of-type(1) {
|
|
.head {
|
|
p::before {
|
|
background-color: #00DAAA;
|
|
}
|
|
}
|
|
}
|
|
|
|
.one:nth-of-type(2) {
|
|
.head {
|
|
p::before {
|
|
background-color: #7BADFF;
|
|
}
|
|
}
|
|
}
|
|
|
|
.one:nth-of-type(3) {
|
|
.head {
|
|
p::before {
|
|
background-color: #FFD271;
|
|
}
|
|
}
|
|
}
|
|
|
|
.one:nth-of-type(4) {
|
|
.head {
|
|
p::before {
|
|
background-color: #FE7B9B;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
.cont2Right {
|
|
width: 820px;
|
|
height: 350px;
|
|
background: #FFFFFF;
|
|
border-radius: 8px;
|
|
|
|
.title {
|
|
width: 100%;
|
|
height: 60px;
|
|
display: flex;
|
|
align-items: center;
|
|
|
|
p {
|
|
width: calc(100% - 240px);
|
|
font-family: PingFangSC;
|
|
font-weight: 600;
|
|
font-size: 16px;
|
|
color: rgba(0, 0, 0, 0.85);
|
|
margin-left: 20px;
|
|
}
|
|
|
|
.tabList {
|
|
width: 240px;
|
|
height: 30px;
|
|
display: flex;
|
|
align-items: center;
|
|
|
|
p {
|
|
width: 83px;
|
|
height: 24px;
|
|
background: #FAFAFA;
|
|
// background: #4188FE;
|
|
border-radius: 12px;
|
|
border: 1px solid rgba(0, 0, 0, 0.15);
|
|
text-align: center;
|
|
line-height: 24px;
|
|
font-size: 12px;
|
|
font-family: PingFangSC;
|
|
font-weight: 400;
|
|
color: rgba(120, 118, 118, 0.85);
|
|
margin-left: 20px;
|
|
margin-top: 20px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.active {
|
|
background: #4188FE;
|
|
color: rgba(255, 255, 255, 0.85);
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
.centerThreeChart {
|
|
width: 100%;
|
|
height: 300px;
|
|
}
|
|
}
|
|
}
|
|
|
|
.cont3,
|
|
.cont4 {
|
|
width: 100%;
|
|
height: auto;
|
|
min-height: 350px;
|
|
background: #FFFFFF;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
|
margin-top: 16px;
|
|
|
|
.title {
|
|
width: 100%;
|
|
height: 56px;
|
|
display: flex;
|
|
align-items: center;
|
|
border-bottom: 1px solid #F5F5F5;
|
|
|
|
p {
|
|
width: auto;
|
|
font-family: PingFangSC;
|
|
font-weight: 600;
|
|
font-size: 16px;
|
|
color: rgba(0, 0, 0, 0.85);
|
|
margin-left: 20px;
|
|
}
|
|
}
|
|
|
|
.cont3Chart,
|
|
.cont4Chart {
|
|
width: 100%;
|
|
height: 280px;
|
|
}
|
|
}
|
|
|
|
.cont4 {
|
|
.title {
|
|
height: auto;
|
|
min-height: 56px;
|
|
padding: 8px 20px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
|
|
.right {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16px;
|
|
|
|
.one {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
|
|
p {
|
|
margin-left: 0;
|
|
white-space: nowrap;
|
|
font-weight: 400;
|
|
font-size: 14px;
|
|
color: rgba(0, 0, 0, 0.65);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.tableCont {
|
|
width: 100%;
|
|
margin-top: 16px;
|
|
background-color: #fff;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
|
|
|
p {
|
|
width: auto;
|
|
height: 56px;
|
|
line-height: 56px;
|
|
font-family: PingFangSC;
|
|
font-weight: 600;
|
|
font-size: 16px;
|
|
color: rgba(0, 0, 0, 0.85);
|
|
margin-left: 20px;
|
|
border-bottom: 1px solid #F5F5F5;
|
|
}
|
|
}
|
|
|
|
// ──── 报告 Tab 头部 ────
|
|
.reportHeader {
|
|
width: 100%;
|
|
background: #FFFFFF;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
|
display: flex;
|
|
align-items: center;
|
|
min-height: 60px;
|
|
padding: 0 20px;
|
|
gap: 0;
|
|
flex-wrap: wrap;
|
|
|
|
.tabBar {
|
|
display: flex;
|
|
align-items: center;
|
|
height: 60px;
|
|
border-right: 1px solid #F0F0F0;
|
|
padding-right: 20px;
|
|
margin-right: 20px;
|
|
flex-shrink: 0;
|
|
|
|
.tabItem {
|
|
height: 32px;
|
|
line-height: 32px;
|
|
padding: 0 18px;
|
|
font-family: PingFangSC;
|
|
font-weight: 400;
|
|
font-size: 14px;
|
|
color: rgba(0, 0, 0, 0.65);
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
margin-right: 4px;
|
|
white-space: nowrap;
|
|
|
|
&:hover {
|
|
color: #4188FE;
|
|
background: rgba(65, 136, 254, 0.06);
|
|
}
|
|
|
|
&.active {
|
|
color: #4188FE;
|
|
font-weight: 600;
|
|
background: rgba(65, 136, 254, 0.1);
|
|
}
|
|
}
|
|
}
|
|
|
|
.tabFilter {
|
|
display: flex;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
gap: 8px;
|
|
flex: 1;
|
|
padding: 8px 0;
|
|
|
|
.filterLabel {
|
|
font-family: PingFangSC;
|
|
font-weight: 400;
|
|
font-size: 14px;
|
|
color: rgba(0, 0, 0, 0.65);
|
|
white-space: nowrap;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ──── 嵌入子报告 ────
|
|
.embeddedReport {
|
|
width: 100%;
|
|
// margin-top: 0 !important;
|
|
// 重置子组件外边距,让它与当前页风格一致
|
|
:deep(.workOrder),
|
|
:deep(.StudentDataStatistics) {
|
|
width: 100%;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
}
|
|
|
|
// ──── 空状态提示 ────
|
|
.emptyHint {
|
|
width: 100%;
|
|
height: 320px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 16px;
|
|
background: #FFFFFF;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
|
margin-top: 16px;
|
|
|
|
.el-icon {
|
|
font-size: 56px;
|
|
color: #C8C8C8;
|
|
}
|
|
|
|
p {
|
|
font-family: PingFangSC;
|
|
font-weight: 400;
|
|
font-size: 14px;
|
|
color: rgba(0, 0, 0, 0.35);
|
|
}
|
|
}
|
|
|
|
// ──── 骨架屏公共样式 ────
|
|
.skeleton-card {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.chart-skeleton {
|
|
width: 100%;
|
|
height: 260px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 0 24px;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.skeleton-chart-wrap {
|
|
width: 100%;
|
|
height: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: flex-end;
|
|
padding-bottom: 12px;
|
|
}
|
|
|
|
.skeleton-bars {
|
|
display: flex;
|
|
align-items: flex-end;
|
|
justify-content: space-around;
|
|
height: 200px;
|
|
padding: 0 12px;
|
|
|
|
.skeleton-bar-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 0;
|
|
}
|
|
}
|
|
|
|
.skeleton-pie-wrap {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 32px;
|
|
height: 100%;
|
|
padding-top: 20px;
|
|
|
|
.skeleton-pie-legend {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 14px;
|
|
|
|
.skeleton-legend-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
}
|
|
}
|
|
|
|
.skeleton-line-wrap {
|
|
justify-content: center;
|
|
padding: 24px 12px 12px;
|
|
|
|
.skeleton-line-dots {
|
|
display: flex;
|
|
align-items: flex-end;
|
|
justify-content: space-around;
|
|
padding: 0 12px;
|
|
|
|
.skeleton-line-dot {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 4px;
|
|
}
|
|
}
|
|
}
|
|
|
|
.skeleton-table {
|
|
padding: 0 20px 20px;
|
|
|
|
.skeleton-table-header {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 14px 0;
|
|
border-bottom: 1px solid #F5F5F5;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.skeleton-table-row {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 16px 0;
|
|
border-bottom: 1px solid #FAFAFA;
|
|
}
|
|
}
|
|
}
|
|
</style> |