|
|
|
|
@ -0,0 +1,375 @@
|
|
|
|
|
<template>
|
|
|
|
|
<el-dialog
|
|
|
|
|
title="签到记录"
|
|
|
|
|
:visible.sync="isShow"
|
|
|
|
|
width="80%"
|
|
|
|
|
:before-close="handleClose"
|
|
|
|
|
class="sign-list-dialog"
|
|
|
|
|
>
|
|
|
|
|
<!-- 课程信息 -->
|
|
|
|
|
<div v-if="courseInfo" class="course-info-section">
|
|
|
|
|
<h3><i class="el-icon-document" /> 课程信息</h3>
|
|
|
|
|
<div class="info-grid">
|
|
|
|
|
<div class="info-item">
|
|
|
|
|
<span class="label">课程名称:</span>
|
|
|
|
|
<span class="value">{{ courseInfo.name }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div v-if="courseInfo.start_date" class="info-item">
|
|
|
|
|
<span class="label">开始时间:</span>
|
|
|
|
|
<span class="value">{{ courseInfo.start_date }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div v-if="courseInfo.end_date" class="info-item">
|
|
|
|
|
<span class="label">结束时间:</span>
|
|
|
|
|
<span class="value">{{ courseInfo.end_date }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 课表信息 -->
|
|
|
|
|
<div v-if="scheduleInfo" class="schedule-info-section">
|
|
|
|
|
<h3><i class="el-icon-date" /> 课表信息</h3>
|
|
|
|
|
<div class="info-grid">
|
|
|
|
|
<div class="info-item">
|
|
|
|
|
<span class="label">主题:</span>
|
|
|
|
|
<span class="value">{{ scheduleInfo.theme }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="info-item">
|
|
|
|
|
<span class="label">时间:</span>
|
|
|
|
|
<span class="value">{{ scheduleInfo.date }} - {{ scheduleInfo.period }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="info-item">
|
|
|
|
|
<span class="label">地点:</span>
|
|
|
|
|
<span class="value">{{ scheduleInfo.address }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div v-if="scheduleInfo.teacher" class="info-item">
|
|
|
|
|
<span class="label">主讲:</span>
|
|
|
|
|
<span class="value">{{ scheduleInfo.teacher.name }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 查询组 -->
|
|
|
|
|
<div class="search-section">
|
|
|
|
|
<h3><i class="el-icon-search" /> 查询条件</h3>
|
|
|
|
|
<el-form :model="searchForm" :inline="true" class="search-form">
|
|
|
|
|
<el-form-item label="姓名:">
|
|
|
|
|
<el-input
|
|
|
|
|
v-model="searchForm.name"
|
|
|
|
|
placeholder="请输入姓名"
|
|
|
|
|
clearable
|
|
|
|
|
style="width: 200px"
|
|
|
|
|
/>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="联系方式:">
|
|
|
|
|
<el-input
|
|
|
|
|
v-model="searchForm.mobile"
|
|
|
|
|
placeholder="请输入联系方式"
|
|
|
|
|
clearable
|
|
|
|
|
style="width: 200px"
|
|
|
|
|
/>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="签到状态:">
|
|
|
|
|
<el-select
|
|
|
|
|
v-model="searchForm.has_check"
|
|
|
|
|
placeholder="请选择签到状态"
|
|
|
|
|
clearable
|
|
|
|
|
style="width: 150px"
|
|
|
|
|
>
|
|
|
|
|
<el-option label="已签到" :value="1" />
|
|
|
|
|
<el-option label="未签到" :value="0" />
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item>
|
|
|
|
|
<el-button type="primary" icon="el-icon-search" @click="handleSearch">
|
|
|
|
|
查询
|
|
|
|
|
</el-button>
|
|
|
|
|
<el-button icon="el-icon-refresh" @click="handleReset">
|
|
|
|
|
重置
|
|
|
|
|
</el-button>
|
|
|
|
|
<el-button type="success" icon="el-icon-download" @click="handleExport">
|
|
|
|
|
导出
|
|
|
|
|
</el-button>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-form>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 数据表格 -->
|
|
|
|
|
<div class="table-section">
|
|
|
|
|
<h3><i class="el-icon-s-grid" /> 签到记录</h3>
|
|
|
|
|
<xy-table
|
|
|
|
|
ref="signTable"
|
|
|
|
|
:height="400"
|
|
|
|
|
:action="getSignList"
|
|
|
|
|
:req-opt="tableReqOpt"
|
|
|
|
|
:table-item="tableColumns"
|
|
|
|
|
:is-page="true"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</el-dialog>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
import { index as getSignList } from '@/api/sign/index.js'
|
|
|
|
|
import { download } from '@/utils/downloadRequest'
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
name: 'SignList',
|
|
|
|
|
props: {
|
|
|
|
|
courseId: {
|
|
|
|
|
type: [String, Number],
|
|
|
|
|
default: null
|
|
|
|
|
},
|
|
|
|
|
courseContentId: {
|
|
|
|
|
type: [String, Number],
|
|
|
|
|
default: null
|
|
|
|
|
},
|
|
|
|
|
courseInfo: {
|
|
|
|
|
type: Object,
|
|
|
|
|
default: null
|
|
|
|
|
},
|
|
|
|
|
scheduleInfo: {
|
|
|
|
|
type: Object,
|
|
|
|
|
default: null
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
data() {
|
|
|
|
|
return {
|
|
|
|
|
isShow: false,
|
|
|
|
|
searchForm: {
|
|
|
|
|
name: '',
|
|
|
|
|
mobile: '',
|
|
|
|
|
has_check: ''
|
|
|
|
|
},
|
|
|
|
|
tableReqOpt: {
|
|
|
|
|
course_id: this.courseId,
|
|
|
|
|
course_content_id: this.courseContentId || ''
|
|
|
|
|
},
|
|
|
|
|
tableColumns: [
|
|
|
|
|
{
|
|
|
|
|
prop: 'user.name',
|
|
|
|
|
label: '姓名',
|
|
|
|
|
align: 'center',
|
|
|
|
|
width: 120
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
prop: 'user.mobile',
|
|
|
|
|
label: '联系方式',
|
|
|
|
|
align: 'center',
|
|
|
|
|
width: 150
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
prop: 'course_content_check',
|
|
|
|
|
label: '签到状态',
|
|
|
|
|
align: 'center',
|
|
|
|
|
width: 120,
|
|
|
|
|
customFn: (scope) => {
|
|
|
|
|
const hasCheckRecord = scope.course_content_check
|
|
|
|
|
const statusText = hasCheckRecord ? '已签到' : '未签到'
|
|
|
|
|
const statusType = hasCheckRecord ? 'success' : 'danger'
|
|
|
|
|
return (
|
|
|
|
|
<el-tag type={statusType} size='small'>
|
|
|
|
|
{statusText}
|
|
|
|
|
</el-tag>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
prop: 'course_content_check.created_at',
|
|
|
|
|
label: '签到时间',
|
|
|
|
|
align: 'center',
|
|
|
|
|
// width: 180
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
watch: {
|
|
|
|
|
courseId(newVal) {
|
|
|
|
|
this.tableReqOpt.course_id = newVal
|
|
|
|
|
},
|
|
|
|
|
courseContentId(newVal) {
|
|
|
|
|
this.tableReqOpt.course_content_id = newVal || ''
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
methods: {
|
|
|
|
|
// 显示弹窗
|
|
|
|
|
show() {
|
|
|
|
|
this.isShow = true
|
|
|
|
|
this.$nextTick(() => {
|
|
|
|
|
this.refreshTable()
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 关闭弹窗
|
|
|
|
|
handleClose() {
|
|
|
|
|
this.isShow = false
|
|
|
|
|
this.resetForm()
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 查询
|
|
|
|
|
handleSearch() {
|
|
|
|
|
this.tableReqOpt = {
|
|
|
|
|
...this.tableReqOpt,
|
|
|
|
|
...this.searchForm
|
|
|
|
|
}
|
|
|
|
|
this.refreshTable()
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 重置
|
|
|
|
|
handleReset() {
|
|
|
|
|
this.resetForm()
|
|
|
|
|
this.tableReqOpt = {
|
|
|
|
|
course_id: this.courseId,
|
|
|
|
|
course_content_id: this.courseContentId || ''
|
|
|
|
|
}
|
|
|
|
|
this.refreshTable()
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 导出
|
|
|
|
|
handleExport() {
|
|
|
|
|
if (!this.courseId && !this.courseContentId) {
|
|
|
|
|
this.$message.warning('缺少课程或课表信息,无法导出')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 构建导出字段映射
|
|
|
|
|
const exportFields = {
|
|
|
|
|
'user.name': '姓名',
|
|
|
|
|
'user.mobile': '联系方式',
|
|
|
|
|
'course_content_check_text': '签到状态',
|
|
|
|
|
'course_content_check_created_at': '签到时间'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 构建导出参数
|
|
|
|
|
const exportParams = {
|
|
|
|
|
...this.searchForm,
|
|
|
|
|
course_id: this.courseId,
|
|
|
|
|
course_content_id: this.courseContentId || '',
|
|
|
|
|
export_fields: exportFields,
|
|
|
|
|
is_export: 1,
|
|
|
|
|
clear: 1,
|
|
|
|
|
page: 1,
|
|
|
|
|
page_size: 999
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 生成文件名
|
|
|
|
|
let fileName = '签到记录'
|
|
|
|
|
if (this.courseInfo && this.courseInfo.name) {
|
|
|
|
|
fileName = `${this.courseInfo.name}-签到记录`
|
|
|
|
|
} else if (this.scheduleInfo && this.scheduleInfo.theme) {
|
|
|
|
|
fileName = `${this.scheduleInfo.theme}-签到记录`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 调用下载API
|
|
|
|
|
download(
|
|
|
|
|
'/api/admin/course-content-check/index',
|
|
|
|
|
'get',
|
|
|
|
|
exportParams,
|
|
|
|
|
`${fileName}.xlsx`
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 重置表单
|
|
|
|
|
resetForm() {
|
|
|
|
|
this.searchForm = {
|
|
|
|
|
name: '',
|
|
|
|
|
mobile: '',
|
|
|
|
|
has_check: ''
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 刷新表格
|
|
|
|
|
refreshTable() {
|
|
|
|
|
if (this.$refs.signTable) {
|
|
|
|
|
this.$refs.signTable.getTableData(true)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 获取签到列表数据
|
|
|
|
|
getSignList(params) {
|
|
|
|
|
return getSignList(params)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
.sign-list-dialog {
|
|
|
|
|
.course-info-section,
|
|
|
|
|
.schedule-info-section,
|
|
|
|
|
.search-section,
|
|
|
|
|
.table-section {
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
padding: 15px;
|
|
|
|
|
background: #f8f9fa;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
border: 1px solid #e9ecef;
|
|
|
|
|
|
|
|
|
|
h3 {
|
|
|
|
|
margin: 0 0 15px 0;
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
color: #2c3e50;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
|
|
|
|
i {
|
|
|
|
|
color: #3498db;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.info-grid {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
|
|
|
|
.info-item {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
|
|
.label {
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
color: #6c757d;
|
|
|
|
|
margin-right: 8px;
|
|
|
|
|
min-width: 80px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.value {
|
|
|
|
|
color: #2c3e50;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.search-form {
|
|
|
|
|
.el-form-item {
|
|
|
|
|
margin-bottom: 15px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.table-section {
|
|
|
|
|
h3 {
|
|
|
|
|
margin-bottom: 15px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
::v-deep .el-dialog__body {
|
|
|
|
|
padding: 20px;
|
|
|
|
|
max-height: 70vh;
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
::v-deep .el-dialog__header {
|
|
|
|
|
background: #f8f9fa;
|
|
|
|
|
border-bottom: 1px solid #e9ecef;
|
|
|
|
|
padding: 15px 20px;
|
|
|
|
|
|
|
|
|
|
.el-dialog__title {
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
color: #2c3e50;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|