|
|
|
|
@ -3,30 +3,18 @@
|
|
|
|
|
<!-- 筛选区 -->
|
|
|
|
|
<div class="filter-bar">
|
|
|
|
|
<el-date-picker
|
|
|
|
|
v-model="filters.dateRange"
|
|
|
|
|
type="daterange"
|
|
|
|
|
range-separator="至"
|
|
|
|
|
start-placeholder="开始日期"
|
|
|
|
|
end-placeholder="结束日期"
|
|
|
|
|
v-model="filters.attDate"
|
|
|
|
|
type="date"
|
|
|
|
|
placeholder="选择日期"
|
|
|
|
|
size="default"
|
|
|
|
|
/>
|
|
|
|
|
<el-select v-model="filters.department" placeholder="选择学院" clearable size="default" style="width: 150px">
|
|
|
|
|
<el-option label="计算机学院" value="cs" />
|
|
|
|
|
<el-option label="数学学院" value="math" />
|
|
|
|
|
<el-option label="外国语学院" value="english" />
|
|
|
|
|
</el-select>
|
|
|
|
|
<el-select v-model="filters.class" placeholder="选择班级" clearable size="default" style="width: 150px">
|
|
|
|
|
<el-option label="2021级1班" value="c1" />
|
|
|
|
|
<el-option label="2022级2班" value="c2" />
|
|
|
|
|
<el-option label="2023级1班" value="c3" />
|
|
|
|
|
</el-select>
|
|
|
|
|
<el-input
|
|
|
|
|
v-model="filters.keyword"
|
|
|
|
|
placeholder="搜索课程/班级..."
|
|
|
|
|
placeholder="搜索课程/授课老师/教室"
|
|
|
|
|
:prefix-icon="Search"
|
|
|
|
|
clearable
|
|
|
|
|
size="default"
|
|
|
|
|
style="width: 200px"
|
|
|
|
|
style="width: 240px"
|
|
|
|
|
/>
|
|
|
|
|
<el-button type="primary" :icon="Search" @click="handleSearch">搜索</el-button>
|
|
|
|
|
<el-button @click="handleReset">重置</el-button>
|
|
|
|
|
@ -55,9 +43,14 @@
|
|
|
|
|
<!-- 数据表格 -->
|
|
|
|
|
<el-table :data="tableData" stripe v-loading="loading">
|
|
|
|
|
<el-table-column prop="courseName" label="课程名称" min-width="150" />
|
|
|
|
|
<el-table-column prop="teacher" label="授课教师" width="100" />
|
|
|
|
|
<el-table-column prop="classroom" label="教室" width="120" />
|
|
|
|
|
<el-table-column prop="time" label="上课时间" width="170" sortable />
|
|
|
|
|
<el-table-column prop="teacher" label="授课教师" width="100" align="center" />
|
|
|
|
|
<el-table-column prop="classroom" label="教室" width="120" align="center" />
|
|
|
|
|
<el-table-column prop="time" label="上课时间" width="170" align="center" sortable>
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<span v-html="row.time"></span>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column prop="total" label="应到" width="70" align="center" />
|
|
|
|
|
<el-table-column prop="actual" label="实到" width="70" align="center" />
|
|
|
|
|
<el-table-column prop="absentCount" label="缺勤" width="70" align="center">
|
|
|
|
|
@ -81,7 +74,7 @@
|
|
|
|
|
<el-pagination
|
|
|
|
|
v-model:current-page="pagination.current"
|
|
|
|
|
v-model:page-size="pagination.pageSize"
|
|
|
|
|
:total="tableData.length"
|
|
|
|
|
:total="total"
|
|
|
|
|
:page-sizes="[10, 20, 50]"
|
|
|
|
|
layout="total, sizes, prev, pager, next"
|
|
|
|
|
background
|
|
|
|
|
@ -90,25 +83,65 @@
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
import { ref, reactive, computed } from 'vue'
|
|
|
|
|
import { ref, reactive, computed, onMounted, watch } from 'vue'
|
|
|
|
|
import { ElMessage } from 'element-plus'
|
|
|
|
|
import { Search } from '@element-plus/icons-vue'
|
|
|
|
|
import { getTaskPage } from '@/api/attendance'
|
|
|
|
|
|
|
|
|
|
defineEmits(['showDetail'])
|
|
|
|
|
|
|
|
|
|
const loading = ref(false)
|
|
|
|
|
const filters = reactive({ dateRange: null, department: '', class: '', keyword: '' })
|
|
|
|
|
const filters = reactive({ attDate: null, keyword: '' })
|
|
|
|
|
const pagination = reactive({ current: 1, pageSize: 10 })
|
|
|
|
|
const total = ref(0)
|
|
|
|
|
|
|
|
|
|
/** 将接口记录转为表格行数据 */
|
|
|
|
|
const mapRecord = (item) => ({
|
|
|
|
|
courseName: item.courseName,
|
|
|
|
|
teacher: item.teacherName,
|
|
|
|
|
classroom: item.classroomName,
|
|
|
|
|
time: `${item.startTime || ''}<br/>至<br/>${item.endTime || ''}`,
|
|
|
|
|
total: item.totalCount,
|
|
|
|
|
actual: item.actualCount,
|
|
|
|
|
absentCount: item.absentCount,
|
|
|
|
|
absentRate: item.totalCount ? Math.round((item.absentCount / item.totalCount) * 1000) / 10 : 0
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const tableData = ref([
|
|
|
|
|
{ courseName: '高等数学A', teacher: '王教授', classroom: '301教室', time: '2024-06-01 08:00-09:40', total: 45, actual: 44, absentCount: 1, absentRate: 2.2 },
|
|
|
|
|
{ courseName: '大学英语B', teacher: '李老师', classroom: '205教室', time: '2024-06-01 10:00-11:40', total: 38, actual: 38, absentCount: 0, absentRate: 0 },
|
|
|
|
|
{ courseName: '计算机导论', teacher: '张教授', classroom: '102实验室', time: '2024-06-01 14:00-15:40', total: 52, actual: 49, absentCount: 3, absentRate: 5.8 },
|
|
|
|
|
{ courseName: '线性代数', teacher: '赵教授', classroom: '408教室', time: '2024-05-31 08:00-09:40', total: 40, actual: 38, absentCount: 2, absentRate: 5.0 },
|
|
|
|
|
{ courseName: '马克思原理', teacher: '刘老师', classroom: '大阶梯教室', time: '2024-05-31 10:00-11:40', total: 120, actual: 108, absentCount: 12, absentRate: 10.0 },
|
|
|
|
|
{ courseName: '数据结构', teacher: '陈教授', classroom: '201教室', time: '2024-05-31 14:00-15:40', total: 48, actual: 47, absentCount: 1, absentRate: 2.1 },
|
|
|
|
|
{ courseName: '操作系统', teacher: '杨教授', classroom: '310教室', time: '2024-05-30 08:00-09:40', total: 42, actual: 40, absentCount: 2, absentRate: 4.8 },
|
|
|
|
|
{ courseName: '数据库原理', teacher: '周老师', classroom: '105实验室', time: '2024-05-30 10:00-11:40', total: 36, actual: 36, absentCount: 0, absentRate: 0 }
|
|
|
|
|
])
|
|
|
|
|
const tableData = ref([])
|
|
|
|
|
|
|
|
|
|
const fetchData = async () => {
|
|
|
|
|
loading.value = true
|
|
|
|
|
try {
|
|
|
|
|
const params = {
|
|
|
|
|
current: pagination.current,
|
|
|
|
|
size: pagination.pageSize,
|
|
|
|
|
keyword: filters.keyword
|
|
|
|
|
}
|
|
|
|
|
if (filters.attDate) {
|
|
|
|
|
const d = new Date(filters.attDate)
|
|
|
|
|
params.attDate = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
|
|
|
|
|
}
|
|
|
|
|
const res = await getTaskPage(params)
|
|
|
|
|
const { records, total: totalCount } = res.data
|
|
|
|
|
if (Array.isArray(records)) {
|
|
|
|
|
tableData.value = records.map(mapRecord)
|
|
|
|
|
}
|
|
|
|
|
total.value = totalCount ?? 0
|
|
|
|
|
} catch {
|
|
|
|
|
// 请求失败保持当前数据
|
|
|
|
|
} finally {
|
|
|
|
|
loading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 分页变化时重新请求
|
|
|
|
|
watch(() => pagination.current, () => fetchData())
|
|
|
|
|
watch(() => pagination.pageSize, () => {
|
|
|
|
|
pagination.current = 1
|
|
|
|
|
fetchData()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
onMounted(() => fetchData())
|
|
|
|
|
|
|
|
|
|
const totalShould = computed(() => tableData.value.reduce((s, r) => s + r.total, 0))
|
|
|
|
|
const totalActual = computed(() => tableData.value.reduce((s, r) => s + r.actual, 0))
|
|
|
|
|
@ -120,10 +153,14 @@ const avgAbsentRate = computed(() => {
|
|
|
|
|
|
|
|
|
|
const getAbsentType = (rate) => (rate >= 10 ? 'danger' : rate > 5 ? 'warning' : 'success')
|
|
|
|
|
|
|
|
|
|
const handleSearch = () => ElMessage.success('搜索完成')
|
|
|
|
|
const handleSearch = () => {
|
|
|
|
|
pagination.current = 1
|
|
|
|
|
fetchData()
|
|
|
|
|
}
|
|
|
|
|
const handleReset = () => {
|
|
|
|
|
Object.assign(filters, { dateRange: null, department: '', class: '', keyword: '' })
|
|
|
|
|
ElMessage.info('已重置')
|
|
|
|
|
Object.assign(filters, { attDate: null, keyword: '' })
|
|
|
|
|
pagination.current = 1
|
|
|
|
|
fetchData()
|
|
|
|
|
}
|
|
|
|
|
const handleExport = (row) => ElMessage.success(`正在导出 ${row.courseName} 考勤明细...`)
|
|
|
|
|
</script>
|
|
|
|
|
|