数据统计

master
lion 2 months ago
parent 2a84e9b92f
commit be88d62cc9

@ -8,3 +8,13 @@ export function home(params,isLoading) {
isLoading: true
})
}
export function courseChart(params,isLoading) {
return request({
url: '/api/admin/other/courses-home',
method: 'get',
params,
isLoading: true
})
}

@ -0,0 +1,919 @@
<template>
<div class="statistics-container">
<div class="dashboard-container">
<!-- 筛选条件区域 -->
<div class="filter-section">
<div class="row">
<div class="col-md-6">
<label class="filter-label">时间周期</label>
<el-select v-model="filterForm.timeRange" placeholder="请选择时间周期" @change="handleTimeRangeChange" style="width: 100%">
<el-option label="全周期" value="all"></el-option>
<el-option label="今年" value="thisYear"></el-option>
<el-option label="去年" value="lastYear"></el-option>
<el-option label="自定义时间段" value="custom"></el-option>
</el-select>
</div>
<div class="col-md-6">
<label class="filter-label">自定义时间</label>
<div class="d-flex gap-2">
<el-date-picker
v-model="filterForm.startDate"
type="date"
placeholder="开始日期"
:disabled="filterForm.timeRange !== 'custom'"
style="width: 100%">
</el-date-picker>
<el-date-picker
v-model="filterForm.endDate"
type="date"
placeholder="结束日期"
:disabled="filterForm.timeRange !== 'custom'"
style="width: 100%">
</el-date-picker>
</div>
</div>
</div>
<div class="row mt-3">
<div class="col-12">
<label class="filter-label">课程体系</label>
<div class="course-checkboxes">
<el-checkbox
v-model="filterForm.courseAll"
@change="handleCourseAllChange"
style="font-weight: bold; color: #0f4c75;">
全选
</el-checkbox>
<el-divider></el-divider>
<el-checkbox-group v-model="filterForm.selectedCourses" @change="handleCourseChange">
<el-checkbox
v-for="course in courseOptions"
:key="course.value"
:label="course.value">
{{ course.label }}
</el-checkbox>
</el-checkbox-group>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-12 text-center">
<el-button type="primary" @click="filterStudentData" class="btn-filter">
<i class="el-icon-search"></i> 查询统计
</el-button>
</div>
</div>
</div>
<!-- 学员统计卡片 -->
<div class="stats-container">
<div class="stats-card" :class="stat.cardClass" v-for="(stat, index) in studentStats" :key="index">
<div class="stats-icon">
<i :class="stat.icon"></i>
</div>
<h3>{{ stat.value }}</h3>
<p>{{ stat.label }}</p>
</div>
</div>
<!-- 课程分类明细统计表格 -->
<el-row>
<el-col :span="24">
<div class="table-header">
<h5 class="table-title">
<i class="el-icon-reading"></i> 课程分类明细统计
</h5>
<el-button type="success" @click="exportCourseData" class="btn-export">
<i class="el-icon-download"></i> 导出数据
</el-button>
</div>
<div class="detail-table">
<el-table :data="courseDetailData" :span-method="objectSpanMethod" :header-cell-style="headerCellStyle">
<el-table-column prop="courseSystem" label="课程体系" width="200" align="center"></el-table-column>
<el-table-column prop="totalPeople" label="培养人数(未去重)" width="200" align="center"></el-table-column>
<el-table-column prop="uniquePeople" label="培养人数(课程体系内已去重)" width="280" align="center"></el-table-column>
<el-table-column prop="courseName" label="开课" min-width="200"></el-table-column>
<el-table-column prop="coursePeople" label="课程培养人数" width="150" align="center"></el-table-column>
</el-table>
</div>
</el-col>
</el-row>
<!-- 区域明细统计表格 -->
<el-row style="margin-top: 30px;">
<el-col :span="24">
<div class="table-header">
<h5 class="table-title">
<i class="el-icon-location"></i> 区域明细统计
</h5>
<el-button type="success" @click="exportRegionData" class="btn-export">
<i class="el-icon-download"></i> 导出数据
</el-button>
</div>
<div class="detail-table">
<el-table :data="regionData" style="width: 100%" :header-cell-style="headerCellStyle">
<el-table-column prop="region" label="区域" width="200" align="center"></el-table-column>
<el-table-column prop="totalPeople" label="培养人数(未去重)" align="center"></el-table-column>
<el-table-column prop="uniquePeople" label="培养人数(已去重)" align="center"></el-table-column>
</el-table>
</div>
</el-col>
</el-row>
</div>
</div>
</template>
<script>
import { index as courseTypeIndex } from '@/api/course/courseType.js'
import { courseChart } from '@/api/homeChart.js'
import * as XLSX from "xlsx";
export default {
name: 'Statistics',
components: {},
data() {
return {
filterForm: {
timeRange: 'all',
startDate: '',
endDate: '',
courseAll: false,
selectedCourses: []
},
courseOptions: [],
courseTypeList: [], //
studentStats: [
{
icon: 'el-icon-user-solid',
value: '1,247',
label: '报名人数(未去重)',
cardClass: 'student-card-1'
},
{
icon: 'el-icon-s-check',
value: '1,156',
label: '培养人数(未去重)',
cardClass: 'student-card-2'
},
{
icon: 'el-icon-s-custom',
value: '892',
label: '培养人数(已去重)',
cardClass: 'student-card-3'
},
{
icon: 'el-icon-date',
value: '56',
label: '开课场次',
cardClass: 'student-card-4'
},
{
icon: 'el-icon-c-scale-to-original',
value: '89',
label: '开课天数',
cardClass: 'student-card-5'
}
],
courseDetailData: [],
regionData: [
{ region: '吴中区', totalPeople: 125, uniquePeople: 98 },
{ region: '相城区', totalPeople: 82, uniquePeople: 65 },
{ region: '昆山市', totalPeople: 74, uniquePeople: 58 },
{ region: '太仓市', totalPeople: 74, uniquePeople: 62 },
{ region: '姑苏区', totalPeople: 65, uniquePeople: 52 }
]
}
},
mounted() {
this.initDates()
this.getCourseTypeList()
//
this.getCourseChart()
},
methods: {
async getCourseChart() {
try {
// ID
const courseTypeIds = this.filterForm.selectedCourses.length > 0
? this.filterForm.selectedCourses.join(',')
: ''
//
const startDate = this.formatDate(this.filterForm.startDate)
const endDate = this.formatDate(this.filterForm.endDate)
const res = await courseChart({
timeRange: this.filterForm.timeRange,
start_date: startDate,
end_date: endDate,
course_type_id: courseTypeIds
})
console.log('课程图表数据:', res)
console.log('日期参数:', { startDate, endDate })
if (res) {
//
this.updateStatisticsData(res)
} else {
this.$message.error('获取课程图表数据失败')
}
} catch (error) {
console.error('获取课程图表数据失败:', error)
this.$message.error('获取课程图表数据失败')
}
},
//
initDates() {
//
this.filterForm.startDate = ''
this.filterForm.endDate = ''
},
//
handleTimeRangeChange(value) {
if (value === 'all') {
//
this.filterForm.startDate = ''
this.filterForm.endDate = ''
} else if (value === 'thisYear') {
//
const today = new Date()
const thisYear = today.getFullYear()
this.filterForm.startDate = new Date(thisYear, 0, 1) // 11
this.filterForm.endDate = today
} else if (value === 'lastYear') {
//
const today = new Date()
const lastYear = today.getFullYear() - 1
this.filterForm.startDate = new Date(lastYear, 0, 1) // 11
this.filterForm.endDate = new Date(lastYear, 11, 31) // 1231
} else if (value === 'custom') {
//
//
if (!this.filterForm.startDate || !this.filterForm.endDate) {
const today = new Date()
const thirtyDaysAgo = new Date(today.getTime() - (30 * 24 * 60 * 60 * 1000))
this.filterForm.startDate = thirtyDaysAgo
this.filterForm.endDate = today
}
}
},
// YYYY-MM-DD
formatDate(date) {
if (!date) return ''
if (typeof date === 'string') return date
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
},
//
handleCourseAllChange(value) {
if (value) {
this.filterForm.selectedCourses = this.courseOptions.map(course => course.value)
} else {
this.filterForm.selectedCourses = []
}
},
//
handleCourseChange(value) {
const allSelected = value.length === this.courseOptions.length
const noneSelected = value.length === 0
if (allSelected) {
this.filterForm.courseAll = true
} else if (noneSelected) {
this.filterForm.courseAll = false
} else {
this.filterForm.courseAll = false
}
},
//
filterStudentData() {
const params = {
timeRange: this.filterForm.timeRange,
startDate: this.filterForm.startDate,
endDate: this.filterForm.endDate,
selectedCourses: this.filterForm.selectedCourses
}
console.log('学员数据筛选:', params)
//
this.getCourseChart()
},
//
exportCourseData() {
if (!this.courseDetailData || this.courseDetailData.length === 0) {
this.$message.warning('暂无数据可导出')
return
}
try {
// Excel
const excelData = [
['课程体系', '培养人数(未去重)', '培养人数(课程体系内已去重)', '开课', '课程培养人数']
]
//
this.courseDetailData.forEach(row => {
excelData.push([
row.courseSystem,
row.totalPeople,
row.uniquePeople,
row.courseName,
row.coursePeople
])
})
// 簿
const wb = XLSX.utils.book_new()
const ws = XLSX.utils.aoa_to_sheet(excelData)
//
const colWidths = [
{ wch: 20 }, //
{ wch: 18 }, //
{ wch: 22 }, //
{ wch: 30 }, //
{ wch: 15 } //
]
ws['!cols'] = colWidths
// 簿
XLSX.utils.book_append_sheet(wb, ws, '课程分类明细统计')
//
const timeRangeText = this.filterForm.timeRange === 'all' ? '全周期' :
this.filterForm.timeRange === 'thisYear' ? '今年' :
this.filterForm.timeRange === 'lastYear' ? '去年' : '自定义时间'
const fileName = `课程分类明细统计_${timeRangeText}_${new Date().toISOString().slice(0, 10)}.xlsx`
//
XLSX.writeFile(wb, fileName)
this.$message.success('课程分类明细数据导出成功!')
} catch (error) {
console.error('导出失败:', error)
this.$message.error('导出失败,请重试')
}
},
//
exportRegionData() {
if (!this.regionData || this.regionData.length === 0) {
this.$message.warning('暂无数据可导出')
return
}
try {
// Excel
const excelData = [
['区域', '培养人数(未去重)', '培养人数(已去重)']
]
//
this.regionData.forEach(row => {
excelData.push([
row.region,
row.totalPeople,
row.uniquePeople
])
})
// 簿
const wb = XLSX.utils.book_new()
const ws = XLSX.utils.aoa_to_sheet(excelData)
//
const colWidths = [
{ wch: 20 }, //
{ wch: 18 }, //
{ wch: 18 } //
]
ws['!cols'] = colWidths
// 簿
XLSX.utils.book_append_sheet(wb, ws, '区域明细统计')
//
const timeRangeText = this.filterForm.timeRange === 'all' ? '全周期' :
this.filterForm.timeRange === 'thisYear' ? '今年' :
this.filterForm.timeRange === 'lastYear' ? '去年' : '自定义时间'
const fileName = `区域明细统计_${timeRangeText}_${new Date().toISOString().slice(0, 10)}.xlsx`
//
XLSX.writeFile(wb, fileName)
this.$message.success('区域明细数据导出成功!')
} catch (error) {
console.error('导出失败:', error)
this.$message.error('导出失败,请重试')
}
},
//
updateStatisticsData(data) {
// API
if (data && data.list) {
//
this.studentStats[0].value = data.list.course_signs_total || '0'
//
this.studentStats[1].value = data.list.course_signs_pass || '0'
//
this.studentStats[2].value = data.list.course_signs_pass_unique || '0'
//
this.studentStats[3].value = data.list.course_total || '0'
//
this.studentStats[4].value = data.list.course_day_total || '0'
}
//
if (data && data.courseTypesSum && Array.isArray(data.courseTypesSum)) {
//
const groupedData = {}
data.courseTypesSum.forEach(item => {
const courseType = item.course_type
if (!groupedData[courseType]) {
//
groupedData[courseType] = {
courseSystem: courseType,
totalPeople: item.course_type_signs_pass || 0,
uniquePeople: item.course_type_signs_pass_unique || 0,
courses: []
}
}
//
groupedData[courseType].courses.push({
courseName: item.course_name || '',
coursePeople: item.course_signs_pass || 0
})
})
//
this.courseDetailData = []
Object.values(groupedData).forEach(group => {
//
group.courses.forEach((course, index) => {
this.courseDetailData.push({
courseSystem: group.courseSystem,
totalPeople: group.totalPeople,
uniquePeople: group.uniquePeople,
courseName: course.courseName,
coursePeople: course.coursePeople,
isFirstRow: index === 0 //
})
})
})
}
//
if (data && data.areas && Array.isArray(data.areas)) {
this.regionData = data.areas.map(item => ({
region: item.value || '',
totalPeople: item.course_signs_pass || 0,
uniquePeople: item.course_signs_pass_unique || 0
}))
}
console.log('统计数据已更新:', this.studentStats)
console.log('课程分类明细数据已更新:', this.courseDetailData)
console.log('区域明细数据已更新:', this.regionData)
},
//
async getCourseTypeList() {
try {
const res = await courseTypeIndex({
page: 1,
page_size: 999
})
if (res && res.data) {
this.courseTypeList = res.data
this.courseOptions = this.courseTypeList.map(item => ({
label: item.name,
value: item.id
}))
} else {
this.$message.error('获取课程体系列表失败')
}
} catch (error) {
console.error('获取课程体系列表失败:', error)
this.$message.error('获取课程体系列表失败')
}
},
//
objectSpanMethod({ row, column, rowIndex, columnIndex }) {
// 3
if (columnIndex === 0 || columnIndex === 1 || columnIndex === 2) {
//
let currentCourseSystem = this.courseDetailData[rowIndex].courseSystem
//
let rowspan = 1
let colspan = 1
//
for (let i = rowIndex - 1; i >= 0; i--) {
if (this.courseDetailData[i].courseSystem === currentCourseSystem) {
rowspan++
} else {
break
}
}
//
for (let i = rowIndex + 1; i < this.courseDetailData.length; i++) {
if (this.courseDetailData[i].courseSystem === currentCourseSystem) {
rowspan++
} else {
break
}
}
//
if (rowIndex === 0 || this.courseDetailData[rowIndex - 1].courseSystem !== currentCourseSystem) {
return { rowspan: rowspan, colspan: 1 }
} else {
//
return { rowspan: 0, colspan: 0 }
}
}
//
return { rowspan: 1, colspan: 1 }
},
//
headerCellStyle({ row, column, rowIndex, columnIndex }) {
return {
background: 'linear-gradient(135deg, #0f4c75 0%, #1e3c72 100%)',
color: 'white',
fontWeight: '600',
fontSize: '0.99rem',
textAlign: 'center'
};
}
}
}
</script>
<style lang="scss" scoped>
.statistics-container {
// background: linear-gradient(135deg, #0f4c75 0%, #1e3c72 50%, #2a5298 100%);
min-height: 100vh;
font-family: 'Microsoft YaHei', sans-serif;
padding: 20px;
}
.dashboard-container {
background: rgba(255, 255, 255, 0.98);
border-radius: 25px;
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.15);
padding: 35px;
backdrop-filter: blur(10px);
}
.main-title {
background: linear-gradient(135deg, #0f4c75 0%, #00a8ff 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
font-weight: bold;
font-size: 2.25rem;
margin-bottom: 40px;
text-align: center;
i {
background: linear-gradient(135deg, #00a8ff 0%, #0097e6 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
}
.filter-section {
background: linear-gradient(135deg, #f8f9fa 0%, #e3f2fd 100%);
border-radius: 20px;
padding: 25px;
margin-bottom: 35px;
border: 1px solid #e8f4fd;
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.08);
.row {
display: flex;
flex-wrap: wrap;
margin: 0 -10px;
.col-md-6, .col-12 {
padding: 0 10px;
margin-bottom: 20px;
}
.col-md-6 {
flex: 0 0 50%;
max-width: 50%;
}
.col-12 {
flex: 0 0 100%;
max-width: 100%;
}
.mt-3 {
margin-top: 15px;
}
.mt-4 {
margin-top: 20px;
}
.text-center {
text-align: center;
}
}
.d-flex {
display: flex;
gap: 10px;
}
}
.filter-label {
color: #0f4c75;
font-weight: 600;
font-size: 0.9rem;
margin-bottom: 8px;
display: block;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
.course-checkboxes {
background: #f8f9fa;
border-radius: 10px;
padding: 15px;
border: 1px solid #e9ecef;
.el-checkbox {
margin-right: 20px;
margin-bottom: 10px;
}
}
.stats-container {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 20px;
margin-bottom: 30px;
padding: 0 10px;
}
.stats-card {
border-radius: 20px;
padding: 28px 20px;
margin-bottom: 0;
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1);
transition: all 0.4s ease;
position: relative;
overflow: hidden;
text-align: center;
height: 200px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
flex: 1;
min-width: 0;
max-width: calc(20% - 16px);
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, #00a8ff, #0097e6);
}
&:hover {
transform: translateY(-8px);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
}
h3 {
font-size: 2.24rem;
font-weight: bold;
margin-bottom: 15px;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
flex-shrink: 0;
}
p {
font-size: 1.08rem;
opacity: 0.95;
margin: 0;
font-weight: 500;
flex-shrink: 0;
margin-bottom:15px;
}
}
.stats-icon {
font-size: 2.8rem;
opacity: 0.9;
margin-bottom: 15px;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
flex-shrink: 0;
}
/* 学员维度卡片配色 */
.student-card-1 {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.student-card-2 {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
color: white;
}
.student-card-3 {
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
color: white;
}
.student-card-4 {
background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
color: #2c3e50;
}
.student-card-5 {
background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%);
color: #2c3e50;
}
.table-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.table-title {
color: #0f4c75;
font-weight: bold;
margin: 0;
font-size: 1.17rem;
i {
color: #00a8ff;
margin-right: 10px;
}
}
.detail-table {
background: white;
border-radius: 20px;
overflow: hidden;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
border: 1px solid #e8f4fd;
.el-table {
.el-table__header-wrapper {
.el-table__header {
th {
background: linear-gradient(135deg, #0f4c75 0%, #1e3c72 100%) !important;
color: white !important;
border: none !important;
font-weight: 600 !important;
padding: 18px 15px !important;
font-size: 0.99rem !important;
text-align: center !important;
}
}
}
.el-table__body-wrapper {
.el-table__body {
td {
vertical-align: middle !important;
border-color: #f1f5f9 !important;
padding: 15px !important;
color: #2c3e50 !important;
font-weight: 500 !important;
font-size: 0.9rem !important;
}
tr:hover {
background: linear-gradient(135deg, #f8f9fa 0%, #e3f2fd 100%) !important;
transform: scale(1.01);
transition: all 0.3s ease;
}
}
}
}
}
/* 强制覆盖Element UI的默认表头样式 */
.detail-table .el-table th.el-table__cell {
background: linear-gradient(135deg, #0f4c75 0%, #1e3c72 100%) !important;
color: white !important;
border: none !important;
font-weight: 600 !important;
padding: 18px 15px !important;
font-size: 0.99rem !important;
text-align: center !important;
}
.detail-table .el-table td.el-table__cell {
vertical-align: middle !important;
border-color: #f1f5f9 !important;
padding: 15px !important;
color: #2c3e50 !important;
font-weight: 500 !important;
font-size: 0.9rem !important;
}
.detail-table .el-table tbody tr:hover > td.el-table__cell {
background: linear-gradient(135deg, #f8f9fa 0%, #e3f2fd 100%) !important;
}
.btn-filter {
background: linear-gradient(135deg, #00a8ff 0%, #0097e6 100%);
border: none;
border-radius: 30px;
padding: 12px 30px;
color: white;
font-weight: 600;
font-size: 0.99rem;
transition: all 0.3s ease;
box-shadow: 0 5px 15px rgba(0, 168, 255, 0.3);
&:hover {
transform: translateY(-3px);
box-shadow: 0 8px 25px rgba(0, 168, 255, 0.4);
background: linear-gradient(135deg, #0097e6 0%, #0088d4 100%);
}
}
.btn-export {
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
border: none;
border-radius: 20px;
padding: 8px 20px;
color: white;
font-weight: 600;
font-size: 0.9rem;
transition: all 0.3s ease;
box-shadow: 0 3px 10px rgba(40, 167, 69, 0.3);
&:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(40, 167, 69, 0.4);
background: linear-gradient(135deg, #20c997 0%, #17a2b8 100%);
}
i {
margin-right: 6px;
}
}
/* 响应式布局 */
@media (max-width: 767.98px) {
.stats-container {
flex-direction: column;
align-items: center;
}
.stats-card {
width: 100%;
margin-bottom: 15px;
}
.table-header {
flex-direction: column;
align-items: flex-start;
.btn-export {
margin-top: 10px;
}
}
}
</style>

@ -0,0 +1,804 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>数据统计页面</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
<style>
body {
background: linear-gradient(135deg, #0f4c75 0%, #1e3c72 50%, #2a5298 100%);
min-height: 100vh;
font-family: 'Microsoft YaHei', sans-serif;
}
.dashboard-container {
background: rgba(255, 255, 255, 0.98);
border-radius: 25px;
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.15);
margin: 20px;
padding: 35px;
backdrop-filter: blur(10px);
}
.section-title {
color: #0f4c75;
font-weight: bold;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 4px solid #e8f4fd;
position: relative;
font-size: 1.35rem;
}
.section-title::after {
content: '';
position: absolute;
bottom: -4px;
left: 0;
width: 60px;
height: 4px;
background: linear-gradient(90deg, #00a8ff, #0097e6);
border-radius: 2px;
}
.filter-section {
background: linear-gradient(135deg, #f8f9fa 0%, #e3f2fd 100%);
border-radius: 20px;
padding: 25px;
margin-bottom: 35px;
border: 1px solid #e8f4fd;
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.08);
}
.stats-card {
border-radius: 20px;
padding: 24px;
margin-bottom: 25px;
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1);
transition: all 0.4s ease;
position: relative;
overflow: hidden;
}
.stats-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, #00a8ff, #0097e6);
}
.stats-card:hover {
transform: translateY(-8px);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
}
.stats-card h3 {
font-size: 2.24rem;
font-weight: bold;
margin-bottom: 15px;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.stats-card p {
font-size: 1.08rem;
opacity: 0.95;
margin: 0;
font-weight: 500;
}
.stats-icon {
font-size: 2.8rem;
opacity: 0.9;
margin-bottom: 20px;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
/* 学员维度卡片配色 */
.stats-card.student-card-1 {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.stats-card.student-card-2 {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
color: white;
}
.stats-card.student-card-3 {
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
color: white;
}
.stats-card.student-card-4 {
background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
color: #2c3e50;
}
.stats-card.student-card-5 {
background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%);
color: #2c3e50;
}
/* 课程维度卡片配色 */
.stats-card.course-card-1 {
background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
color: #2c3e50;
}
.stats-card.course-card-2 {
background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%);
color: #2c3e50;
}
.stats-card.course-card-3 {
background: linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%);
color: #2c3e50;
}
.stats-card.course-card-4 {
background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
color: #2c3e50;
}
/* 师资维度卡片配色 */
.stats-card.teacher-card-1 {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.stats-card.teacher-card-2 {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
color: white;
}
.stats-card.teacher-card-3 {
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
color: white;
}
.detail-table {
background: white;
border-radius: 20px;
overflow: hidden;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
border: 1px solid #e8f4fd;
}
.table th {
background: linear-gradient(135deg, #0f4c75 0%, #1e3c72 100%);
color: white;
border: none;
font-weight: 600;
padding: 18px 15px;
font-size: 0.99rem;
}
.table td {
vertical-align: middle;
border-color: #f1f5f9;
padding: 15px;
color: #2c3e50;
font-weight: 500;
font-size: 0.9rem;
}
.table-hover tbody tr:hover {
background: linear-gradient(135deg, #f8f9fa 0%, #e3f2fd 100%);
transform: scale(1.01);
transition: all 0.3s ease;
}
.btn-filter {
background: linear-gradient(135deg, #00a8ff 0%, #0097e6 100%);
border: none;
border-radius: 30px;
padding: 12px 30px;
color: white;
font-weight: 600;
font-size: 0.99rem;
transition: all 0.3s ease;
box-shadow: 0 5px 15px rgba(0, 168, 255, 0.3);
}
.btn-filter:hover {
transform: translateY(-3px);
box-shadow: 0 8px 25px rgba(0, 168, 255, 0.4);
background: linear-gradient(135deg, #0097e6 0%, #0088d4 100%);
}
.form-control, .form-select {
border-radius: 15px;
border: 2px solid #e8f4fd;
transition: all 0.3s ease;
padding: 12px 18px;
font-size: 0.9rem;
background: rgba(255, 255, 255, 0.9);
}
.form-control:focus, .form-select:focus {
border-color: #00a8ff;
box-shadow: 0 0 0 0.2rem rgba(0, 168, 255, 0.25);
background: white;
}
/* 多选下拉框样式优化 */
select[multiple] {
min-height: 120px;
}
select[multiple] option {
padding: 8px 12px;
border-bottom: 1px solid #f1f5f9;
}
select[multiple] option:checked {
background: linear-gradient(135deg, #00a8ff 0%, #0097e6 100%);
color: white;
}
select[multiple] option:hover {
background: #e3f2fd;
}
/* 课程体系checkbox样式 */
.course-checkboxes {
background: #f8f9fa;
border-radius: 10px;
padding: 15px;
border: 1px solid #e9ecef;
}
.course-checkboxes .form-check {
margin-right: 20px;
margin-bottom: 10px;
}
.course-checkboxes .form-check-input {
border-color: #00a8ff;
margin-right: 8px;
}
.course-checkboxes .form-check-input:checked {
background-color: #00a8ff;
border-color: #00a8ff;
}
.course-checkboxes .form-check-input:focus {
box-shadow: 0 0 0 0.2rem rgba(0, 168, 255, 0.25);
}
.course-checkboxes .form-check-label {
color: #2c3e50;
font-weight: 500;
cursor: pointer;
}
/* 导出按钮样式 */
.btn-export {
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
border: none;
border-radius: 20px;
padding: 8px 20px;
color: white;
font-weight: 600;
font-size: 0.9rem;
transition: all 0.3s ease;
box-shadow: 0 3px 10px rgba(40, 167, 69, 0.3);
}
.btn-export:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(40, 167, 69, 0.4);
background: linear-gradient(135deg, #20c997 0%, #17a2b8 100%);
}
.btn-export i {
margin-right: 6px;
}
/* 5等分宽度布局 */
.col-md-2-4 {
flex: 0 0 20%;
max-width: 20%;
}
@media (max-width: 767.98px) {
.col-md-2-4 {
flex: 0 0 100%;
max-width: 100%;
margin-bottom: 15px;
}
}
/* 课程分类明细统计表格样式 */
.course-system-row {
background: linear-gradient(135deg, #f8f9fa 0%, #e3f2fd 100%);
}
.course-system-row:hover {
background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
}
.course-item-row {
background: white;
}
.course-item-row:hover {
background: #f8f9fa;
}
.course-system-name {
font-weight: bold;
text-align: center;
vertical-align: middle;
}
.course-item {
padding-left: 20px;
color: #2c3e50;
font-weight: 500;
}
.course-item-row .course-item {
border-left: 3px solid #00a8ff;
margin-left: 10px;
}
.table th {
background: linear-gradient(135deg, #0f4c75 0%, #1e3c72 100%);
color: white;
border: none;
font-weight: 600;
padding: 18px 15px;
font-size: 0.99rem;
text-align: center;
}
.table td {
vertical-align: middle;
border-color: #f1f5f9;
padding: 15px;
color: #2c3e50;
font-weight: 500;
font-size: 0.9rem;
}
.form-label {
color: #0f4c75;
font-weight: 600;
font-size: 0.9rem;
margin-bottom: 8px;
}
.table-title {
color: #0f4c75;
font-weight: bold;
margin-bottom: 20px;
font-size: 1.17rem;
}
.table-title i {
color: #00a8ff;
margin-right: 10px;
}
hr {
border-color: #e8f4fd;
border-width: 3px;
border-radius: 2px;
opacity: 0.8;
}
.main-title {
background: linear-gradient(135deg, #0f4c75 0%, #00a8ff 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
font-weight: bold;
font-size: 2.25rem;
margin-bottom: 40px;
}
.main-title i {
background: linear-gradient(135deg, #00a8ff 0%, #0097e6 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
</style>
</head>
<body>
<div class="container-fluid">
<div class="dashboard-container">
<div class="filter-section">
<div class="row">
<div class="col-md-6">
<label class="form-label">时间周期</label>
<select class="form-select" id="studentTimeRange">
<option value="all" selected>全周期</option>
<option value="thisYear">今年</option>
<option value="lastYear">去年</option>
<option value="custom">自定义时间段</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label">自定义时间</label>
<div class="d-flex gap-2">
<input type="date" class="form-control" id="studentStartDate" placeholder="开始日期">
<input type="date" class="form-control" id="studentEndDate" placeholder="结束日期">
</div>
</div>
</div>
<div class="row mt-3">
<div class="col-12">
<label class="form-label">课程体系</label>
<div class="course-checkboxes">
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="courseAll" value="all">
<label class="form-check-label" for="courseAll" style="font-weight: bold; color: #0f4c75;">全选</label>
</div>
<hr class="my-2" style="border-color: #dee2e6; opacity: 0.6;">
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="courseIndustry" value="industry">
<label class="form-check-label" for="courseIndustry">产业加速营</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="coursePeak" value="peak">
<label class="form-check-label" for="coursePeak">攀峰班</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="courseSecond" value="second">
<label class="form-check-label" for="courseSecond">第二课堂</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="courseStartup" value="startup">
<label class="form-check-label" for="courseStartup">初创班</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="courseLecture" value="lecture">
<label class="form-check-label" for="courseLecture">科技大讲堂</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="courseTraining" value="training">
<label class="form-check-label" for="courseTraining">人才培训</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="courseAdvanced" value="advanced">
<label class="form-check-label" for="courseAdvanced">高研班</label>
</div>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-12 text-center">
<button class="btn btn-filter" onclick="filterStudentData()">
<i class="bi bi-search"></i> 查询统计
</button>
</div>
</div>
</div>
<!-- 学员统计卡片 -->
<div class="row mb-4">
<div class="col-md-2-4">
<div class="stats-card student-card-1 text-center">
<div class="stats-icon">
<i class="bi bi-person-plus"></i>
</div>
<h3 id="totalRegistrations">1,247</h3>
<p>报名人数(未去重)</p>
</div>
</div>
<div class="col-md-2-4">
<div class="stats-card student-card-2 text-center">
<div class="stats-icon">
<i class="bi bi-clipboard-check"></i>
</div>
<h3 id="totalReviews">1,156</h3>
<p>培养人数(未去重)</p>
</div>
</div>
<div class="col-md-2-4">
<div class="stats-card student-card-3 text-center">
<div class="stats-icon">
<i class="bi bi-people"></i>
</div>
<h3 id="totalReviewPeople">892</h3>
<p>培养人数(已去重)</p>
</div>
</div>
<div class="col-md-2-4">
<div class="stats-card student-card-4 text-center">
<div class="stats-icon">
<i class="bi bi-calendar-event"></i>
</div>
<h3 id="totalCourseSessions">56</h3>
<p>开课场次</p>
</div>
</div>
<div class="col-md-2-4">
<div class="stats-card student-card-5 text-center">
<div class="stats-icon">
<i class="bi bi-calendar-week"></i>
</div>
<h3 id="totalCourseDays">89</h3>
<p>开课天数</p>
</div>
</div>
</div>
<!-- 学员明细表格 -->
<div class="row">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-3">
<h5 class="table-title mb-0">
<i class="bi bi-book"></i> 课程分类明细统计
</h5>
<button class="btn btn-export" onclick="exportCourseData()">
<i class="bi bi-download"></i> 导出数据
</button>
</div>
<div class="detail-table">
<table class="table table-hover mb-0">
<thead>
<tr>
<th style="width: 20%;">课程体系</th>
<th style="width: 18%;">培养人数(未去重)</th>
<th style="width: 18%;">培养人数(课程体系内已去重)</th>
<th style="width: 24%;">开课</th>
<th style="width: 20%;">课程培养人数</th>
</tr>
</thead>
<tbody>
<tr class="course-system-row">
<td rowspan="3" class="course-system-name">
<strong>科技大讲堂</strong>
</td>
<td rowspan="3" class="text-center">824</td>
<td rowspan="3" class="text-center">612</td>
<td class="course-item">智能制造专题讲座</td>
<td class="text-center">45</td>
</tr>
<tr class="course-item-row">
<td class="course-item">数字化转型研讨会</td>
<td class="text-center">38</td>
</tr>
<tr class="course-item-row">
<td class="course-item">科技创新前沿分享</td>
<td class="text-center">73</td>
</tr>
<tr class="course-system-row">
<td rowspan="2" class="course-system-name">
<strong>高研班</strong>
</td>
<td rowspan="2" class="text-center">566</td>
<td rowspan="2" class="text-center">445</td>
<td class="course-item">高级管理研修班</td>
<td class="text-center">52</td>
</tr>
<tr class="course-item-row">
<td class="course-item">战略思维提升班</td>
<td class="text-center">37</td>
</tr>
<tr class="course-system-row">
<td rowspan="2" class="course-system-name">
<strong>产业加速营</strong>
</td>
<td rowspan="2" class="text-center">218</td>
<td rowspan="2" class="text-center">189</td>
<td class="course-item">智能制造加速营</td>
<td class="text-center">28</td>
</tr>
<tr class="course-item-row">
<td class="course-item">生物医药加速营</td>
<td class="text-center">17</td>
</tr>
<tr class="course-system-row">
<td class="course-system-name">
<strong>人才培训</strong>
</td>
<td class="text-center">153</td>
<td class="text-center">134</td>
<td class="course-item">专业技能培训</td>
<td class="text-center">32</td>
</tr>
<tr class="course-system-row">
<td rowspan="2" class="course-system-name">
<strong>第二课堂</strong>
</td>
<td rowspan="2" class="text-center">320</td>
<td rowspan="2" class="text-center">298</td>
<td class="course-item">创新创业实践</td>
<td class="text-center">45</td>
</tr>
<tr class="course-item-row">
<td class="course-item">团队协作训练</td>
<td class="text-center">33</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-3">
<h5 class="table-title mb-0">
<i class="bi bi-geo-alt"></i> 区域明细统计
</h5>
<button class="btn btn-export" onclick="exportRegionData()">
<i class="bi bi-download"></i> 导出数据
</button>
</div>
<div class="detail-table">
<table class="table table-hover mb-0">
<thead>
<tr>
<th>区域</th>
<th>培养人数(未去重)</th>
<th>培养人数(已去重)</th>
</tr>
</thead>
<tbody>
<tr>
<td>吴中区</td>
<td>125</td>
<td>98</td>
</tr>
<tr>
<td>相城区</td>
<td>82</td>
<td>65</td>
</tr>
<tr>
<td>昆山市</td>
<td>74</td>
<td>58</td>
</tr>
<tr>
<td>太仓市</td>
<td>74</td>
<td>62</td>
</tr>
<tr>
<td>姑苏区</td>
<td>65</td>
<td>52</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
// 学员数据筛选
function filterStudentData() {
const timeRange = document.getElementById('studentTimeRange').value;
const courseSystem = getSelectedCourseSystems();
const startDate = document.getElementById('studentStartDate').value;
const endDate = document.getElementById('studentEndDate').value;
console.log('学员数据筛选:', { timeRange, courseSystem, startDate, endDate });
// 这里可以添加实际的数据筛选逻辑
alert('学员数据筛选功能已触发!');
}
// 获取选中的课程体系
function getSelectedCourseSystems() {
const checkboxes = document.querySelectorAll('.course-checkboxes input[type="checkbox"]:checked:not(#courseAll)');
return Array.from(checkboxes).map(checkbox => checkbox.value);
}
// 导出课程分类明细数据
function exportCourseData() {
const timeRange = document.getElementById('studentTimeRange').value;
const courseSystem = getSelectedCourseSystems();
const startDate = document.getElementById('studentStartDate').value;
const endDate = document.getElementById('studentEndDate').value;
console.log('导出课程分类明细数据:', { timeRange, courseSystem, startDate, endDate });
// 这里可以添加实际的导出逻辑比如生成Excel文件
alert('课程分类明细数据导出功能已触发!');
}
// 导出区域明细数据
function exportRegionData() {
const timeRange = document.getElementById('studentTimeRange').value;
const courseSystem = getSelectedCourseSystems();
const startDate = document.getElementById('studentStartDate').value;
const endDate = document.getElementById('studentEndDate').value;
console.log('导出区域明细数据:', { timeRange, courseSystem, startDate, endDate });
// 这里可以添加实际的导出逻辑比如生成Excel文件
alert('区域明细数据导出功能已触发!');
}
// 全选功能
function toggleAllCourses() {
const allCheckbox = document.getElementById('courseAll');
const courseCheckboxes = document.querySelectorAll('.course-checkboxes input[type="checkbox"]:not(#courseAll)');
courseCheckboxes.forEach(checkbox => {
checkbox.checked = allCheckbox.checked;
});
}
// 同步全选状态
function syncAllCheckboxState() {
const allCheckbox = document.getElementById('courseAll');
const courseCheckboxes = document.querySelectorAll('.course-checkboxes input[type="checkbox"]:not(#courseAll)');
const checkedCount = Array.from(courseCheckboxes).filter(checkbox => checkbox.checked).length;
if (checkedCount === 0) {
allCheckbox.checked = false;
allCheckbox.indeterminate = false;
} else if (checkedCount === courseCheckboxes.length) {
allCheckbox.checked = true;
allCheckbox.indeterminate = false;
} else {
allCheckbox.checked = false;
allCheckbox.indeterminate = true;
}
}
// 控制自定义时间输入框的启用/禁用状态
function toggleCustomDateInputs() {
const timeRange = document.getElementById('studentTimeRange').value;
const startDateInput = document.getElementById('studentStartDate');
const endDateInput = document.getElementById('studentEndDate');
if (timeRange === 'custom') {
startDateInput.disabled = false;
endDateInput.disabled = false;
startDateInput.style.opacity = '1';
endDateInput.style.opacity = '1';
} else {
startDateInput.disabled = true;
endDateInput.disabled = true;
startDateInput.style.opacity = '0.5';
endDateInput.style.opacity = '0.5';
}
}
// 页面加载完成后设置事件监听器
document.addEventListener('DOMContentLoaded', function() {
const today = new Date();
const thirtyDaysAgo = new Date(today.getTime() - (30 * 24 * 60 * 60 * 1000));
document.getElementById('studentStartDate').value = thirtyDaysAgo.toISOString().split('T')[0];
document.getElementById('studentEndDate').value = today.toISOString().split('T')[0];
document.getElementById('courseStartDate').value = thirtyDaysAgo.toISOString().split('T')[0];
document.getElementById('courseEndDate').value = today.toISOString().split('T')[0];
// 添加学员维度时间周期选择的事件监听器
document.getElementById('studentTimeRange').addEventListener('change', toggleCustomDateInputs);
// 添加全选checkbox的事件监听器
document.getElementById('courseAll').addEventListener('change', toggleAllCourses);
// 添加课程checkbox的事件监听器
const courseCheckboxes = document.querySelectorAll('.course-checkboxes input[type="checkbox"]:not(#courseAll)');
courseCheckboxes.forEach(checkbox => {
checkbox.addEventListener('change', syncAllCheckboxState);
});
// 初始化时调用一次
toggleCustomDateInputs();
});
</script>
</body>
</html>
Loading…
Cancel
Save