新增开班主流程:实现学校周课表展示、住宿状态直观外露及课次考勤闭环联动

This commit is contained in:
wangzhibiao committed 2026-10-09 15:48:13 +08:00
1 parent 55143af4a5
commit 1149c2b141
6 files changed
+773 -10

No files matched your search

@@ -55,6 +55,18 @@ public class EnrollmentItemVO
/** 备注 */
private String remark;
/** 宿舍楼栋 (当前分配) */
private String dormBuilding;
/** 宿舍房间号 (当前分配) */
private String dormRoom;
/** 床位号 (当前分配) */
private String bedNo;
/** 住宿状态 (0: 预分配, 1: 已入住, 2: 已退宿) */
private Integer accommodationStatus;
public Long getEnrollmentId()
{
return enrollmentId;
@@ -204,4 +216,44 @@ public class EnrollmentItemVO
{
this.remark = remark;
}
public String getDormBuilding()
{
return dormBuilding;
}
public void setDormBuilding(String dormBuilding)
{
this.dormBuilding = dormBuilding;
}
public String getDormRoom()
{
return dormRoom;
}
public void setDormRoom(String dormRoom)
{
this.dormRoom = dormRoom;
}
public String getBedNo()
{
return bedNo;
}
public void setBedNo(String bedNo)
{
this.bedNo = bedNo;
}
public Integer getAccommodationStatus()
{
return accommodationStatus;
}
public void setAccommodationStatus(Integer accommodationStatus)
{
this.accommodationStatus = accommodationStatus;
}
}
@@ -78,9 +78,23 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
e.accommodation_required as accommodationRequired,
e.join_time as joinTime,
e.complete_time as completeTime,
e.remark as remark
e.remark as remark,
d.dorm_building as dormBuilding,
d.dorm_room as dormRoom,
a.bed_no as bedNo,
a.accommodation_status as accommodationStatus
from train_enrollment e
left join train_student s on e.student_id = s.s_id
left join (
select a1.enrollment_id, a1.dorm_id, a1.bed_no, a1.accommodation_status
from train_accommodation a1
inner join (
select enrollment_id, max(accommodation_id) as max_id
from train_accommodation
group by enrollment_id
) a2 on a1.accommodation_id = a2.max_id
) a on e.enrollment_id = a.enrollment_id
left join train_dormitory_info d on a.dorm_id = d.dorm_id
<where>
e.plan_id = #{planId}
<if test="studentName != null and studentName != ''">
+10 -3
View File
@@ -389,15 +389,20 @@ export default {
}).finally(() => { this.loading = false })
},
loadPlans() {
const queryPlanId = this.$route.query.planId
const queryLessonId = this.$route.query.lessonId
listPlan({ pageNum: 1, pageSize: 100 }).then(response => {
this.planOptions = response.rows || []
if (this.planOptions.length > 0 && !this.rosterQuery.planId) {
if (queryPlanId && this.planOptions.some(p => p.planId === queryPlanId)) {
this.rosterQuery.planId = queryPlanId
this.handlePlanChange(queryPlanId, queryLessonId)
} else if (this.planOptions.length > 0 && !this.rosterQuery.planId) {
this.rosterQuery.planId = this.planOptions[0].planId
this.handlePlanChange(this.rosterQuery.planId)
}
}).catch(() => {})
},
handlePlanChange(planId) {
handlePlanChange(planId, targetLessonId) {
this.rosterQuery.lessonId = null
this.rosterList = []
this.lessonOptions = []
@@ -425,7 +430,9 @@ export default {
}
this.lessonOptions = list
if (list.length > 0) {
this.rosterQuery.lessonId = list[0].lessonId
const parsedTarget = targetLessonId ? Number(targetLessonId) : null
const matched = parsedTarget ? list.find(item => item.lessonId === parsedTarget) : null
this.rosterQuery.lessonId = matched ? matched.lessonId : list[0].lessonId
this.loadRoster()
}
}).finally(() => {
@@ -82,6 +82,31 @@
</el-tag>
</template>
</el-table-column>
<el-table-column label="住宿安排" align="center" min-width="170" show-overflow-tooltip>
<template slot-scope="scope">
<div v-if="scope.row.dormBuilding || scope.row.dormRoom" style="display: flex; align-items: center; justify-content: center; gap: 4px;">
<span>{{ scope.row.dormBuilding || '' }} {{ scope.row.dormRoom || '' }} ({{ scope.row.bedNo || '-' }})</span>
<el-tag
size="mini"
:type="scope.row.accommodationStatus === 1 ? 'success' : (scope.row.accommodationStatus === 0 ? 'info' : 'danger')"
>
{{ scope.row.accommodationStatus === 1 ? '在住' : (scope.row.accommodationStatus === 0 ? '预分配' : '已退宿') }}
</el-tag>
</div>
<div v-else-if="scope.row.accommodationRequired === 1">
<el-tag size="mini" type="danger">未分配</el-tag>
<el-button
v-hasPermi="['train:accommodation:add']"
size="mini"
type="text"
icon="el-icon-plus"
style="margin-left: 4px;"
@click="handleOpenAccommodation(scope.row)"
>去分配</el-button>
</div>
<span v-else style="color: #909399; font-size: 12px;">无需住宿</span>
</template>
</el-table-column>
<el-table-column label="参训状态" align="center" prop="enrollmentStatus" width="95">
<template slot-scope="scope">
<el-tag size="mini" :type="statusTagType(scope.row.enrollmentStatus)">
@@ -11,6 +11,7 @@
</div>
<div>
<el-radio-group v-model="viewMode" size="mini" style="margin-right: 10px;">
<el-radio-button label="week">学校周课表</el-radio-button>
<el-radio-button label="timeline">按日日程</el-radio-button>
<el-radio-button label="table">课次明细</el-radio-button>
</el-radio-group>
@@ -30,6 +31,119 @@
</div>
</div>
<!-- 视图零:学校周课表视图 (UC-11 学校风格课表展示) -->
<div v-if="viewMode === 'week'" v-loading="loading" class="school-timetable-view">
<!-- 周切换条 -->
<div class="week-nav-bar">
<el-button-group size="mini">
<el-button icon="el-icon-arrow-left" @click="handlePrevWeek">上一周</el-button>
<el-button @click="handleCurrentOrFirstWeek">本周/首周</el-button>
<el-button @click="handleNextWeek">下一周<i class="el-icon-arrow-right el-icon--right" /></el-button>
</el-button-group>
<span class="week-title-display">
<i class="el-icon-date" style="color: #409EFF; margin-right: 5px;" />
{{ currentWeekDisplayTitle }}
</span>
<span style="font-size: 12px; color: #909399;">
本周课次:{{ currentWeekLessonCount }} 节
</span>
</div>
<!-- 课表格子结构 -->
<div class="school-grid-container">
<table class="school-grid-table">
<thead>
<tr>
<th class="time-col-header">时段 / 星期</th>
<th
v-for="day in currentWeekDays"
:key="day.dateStr"
:class="['day-col-header', { 'is-today': isToday(day.dateStr) }]"
>
<div class="header-day-label">{{ day.label }}</div>
<div class="header-date-label">{{ day.displayDate }}</div>
</th>
</tr>
</thead>
<tbody>
<tr v-for="period in periodList" :key="period.key">
<td class="period-title-cell">
<div class="period-name">{{ period.label }}</div>
<div class="period-time-range">{{ period.timeRange }}</div>
</td>
<td
v-for="day in currentWeekDays"
:key="`${day.dateStr}_${period.key}`"
class="timetable-cell"
>
<!-- 格子中的课次卡片列表 -->
<div
v-for="lesson in getLessonsInCell(day.dateStr, period.key)"
:key="lesson.lessonId"
class="school-lesson-card"
>
<div class="slc-time-tag">
<i class="el-icon-time" />
{{ formatTime(lesson.startTime) }} - {{ formatTime(lesson.endTime) }}
</div>
<div class="slc-course-title" :title="lesson.courseName">
{{ lesson.courseName }}
</div>
<div class="slc-meta">
<span class="meta-row">
<i class="el-icon-user" /> {{ lesson.teacherName || '教师待定' }}
</span>
<span class="meta-row">
<i class="el-icon-location-outline" />
{{ lesson.venueName || lesson.classRoomName || lesson.siteName || '地点待定' }}
</span>
<span v-if="lesson.boardSn" class="meta-row" style="color: #67C23A;">
<i class="el-icon-monitor" /> {{ lesson.boardSn }}
</span>
</div>
<div class="slc-card-actions">
<el-button
v-hasPermi="['train:attendance:list']"
size="mini"
type="text"
icon="el-icon-date"
@click.stop="handleOpenAttendance(lesson)"
>考勤</el-button>
<el-button
v-hasPermi="['train:lesson:edit']"
size="mini"
type="text"
icon="el-icon-edit"
:disabled="isPlanLocked"
@click.stop="handleEditLesson(lesson)"
>修改</el-button>
<el-button
v-hasPermi="['train:lesson:remove']"
size="mini"
type="text"
icon="el-icon-delete"
style="color: #F56C6C;"
:disabled="isPlanLocked"
@click.stop="handleRemoveLesson(lesson)"
>删除</el-button>
</div>
</div>
<!-- 空格子快速排课入口 -->
<div
v-if="getLessonsInCell(day.dateStr, period.key).length === 0 && !isPlanLocked"
class="empty-cell-placeholder"
@click="handleQuickAddLesson(day.dateStr, period.key)"
>
<i class="el-icon-plus" /> 排课
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- 视图一:按日日程视图 -->
<div v-if="viewMode === 'timeline'" v-loading="loading" class="timeline-view">
<div v-if="!timetableData.days || timetableData.days.length === 0" class="empty-tip">
@@ -93,6 +207,13 @@
</div>
</div>
<div class="lesson-actions">
<el-button
v-hasPermi="['train:attendance:list']"
size="mini"
type="text"
icon="el-icon-date"
@click="handleOpenAttendance(lesson)"
>考勤</el-button>
<el-button
v-hasPermi="['train:lesson:edit']"
size="mini"
@@ -143,8 +264,15 @@
</template>
</el-table-column>
<el-table-column label="课次说明" prop="remark" min-width="120" show-overflow-tooltip />
<el-table-column label="操作" align="center" width="130" fixed="right">
<el-table-column label="操作" align="center" width="180" fixed="right">
<template slot-scope="scope">
<el-button
v-hasPermi="['train:attendance:list']"
size="mini"
type="text"
icon="el-icon-date"
@click="handleOpenAttendance(scope.row)"
>考勤</el-button>
<el-button
v-hasPermi="['train:lesson:edit']"
size="mini"
@@ -347,6 +475,112 @@
<el-button @click="dialog.open = false">取 消</el-button>
</div>
</el-dialog>
<!-- 课次学员考勤名册对话框 (第四优先级) -->
<el-dialog
:title="`课次考勤名册 - ${attendanceDialog.lesson.courseName || ''}`"
:visible.sync="attendanceDialog.open"
width="900px"
append-to-body
>
<div v-loading="attendanceDialog.loading">
<!-- 课次基础概要 -->
<el-descriptions :column="3" border size="small" style="margin-bottom: 12px;">
<el-descriptions-item label="课程名称">{{ attendanceDialog.lesson.courseName || '-' }}</el-descriptions-item>
<el-descriptions-item label="授课教师">{{ attendanceDialog.lesson.teacherName || '-' }}</el-descriptions-item>
<el-descriptions-item label="上课地点">{{ attendanceDialog.lesson.venueName || attendanceDialog.lesson.classRoomName || attendanceDialog.lesson.siteName || '-' }}</el-descriptions-item>
<el-descriptions-item label="课次时间" :span="2">
{{ attendanceDialog.lesson.startTime }} ~ {{ attendanceDialog.lesson.endTime }}
</el-descriptions-item>
<el-descriptions-item label="电子班牌">{{ attendanceDialog.lesson.boardSn || '无' }}</el-descriptions-item>
</el-descriptions>
<div style="margin-bottom: 10px; display: flex; justify-content: space-between; align-items: center;">
<div>
<span style="font-weight: bold; font-size: 13px;">应到学员考勤名册</span>
<el-tag size="mini" type="info" style="margin-left: 8px;">共 {{ attendanceDialog.rosterList.length }} 人</el-tag>
</div>
<div>
<el-button size="mini" icon="el-icon-refresh" @click="refreshAttendanceRoster">刷新名册</el-button>
<el-button
size="mini"
type="primary"
plain
icon="el-icon-s-promotion"
@click="goToFullCheckinPage"
>前往考勤中心</el-button>
</div>
</div>
<el-table :data="attendanceDialog.rosterList" border size="small" max-height="360" empty-text="当前课次暂无应到参训学员(学员可能尚未报到或未入班)">
<el-table-column type="index" label="序号" width="50" align="center" />
<el-table-column label="学员姓名" prop="studentName" width="100" align="center" />
<el-table-column label="学员学号" prop="studentId" width="110" align="center" show-overflow-tooltip />
<el-table-column label="选派单位" prop="deptName" min-width="130" show-overflow-tooltip />
<el-table-column label="参训状态" width="85" align="center">
<template slot-scope="scope">
<el-tag size="mini" :type="formatEnrollmentStatusTag(scope.row.enrollmentStatus).type">
{{ formatEnrollmentStatusTag(scope.row.enrollmentStatus).text }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="考勤状态" width="85" align="center">
<template slot-scope="scope">
<el-tag size="mini" :type="formatAttendanceStatusTag(scope.row.attendanceStatus).type">
{{ formatAttendanceStatusTag(scope.row.attendanceStatus).text }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="签到时间" prop="signInTime" width="140" align="center">
<template slot-scope="scope">{{ scope.row.signInTime || '-' }}</template>
</el-table-column>
<el-table-column label="签退时间" prop="signOutTime" width="140" align="center">
<template slot-scope="scope">{{ scope.row.signOutTime || '-' }}</template>
</el-table-column>
<el-table-column label="操作" width="170" align="center" fixed="right">
<template slot-scope="scope">
<el-button
v-if="canRosterSignIn(scope.row)"
v-hasPermi="['train:attendance:checkin']"
size="mini"
type="text"
icon="el-icon-check"
@click="handleRosterSignIn(scope.row)"
>签到</el-button>
<el-button
v-if="canRosterMarkAbsence(scope.row)"
v-hasPermi="['train:attendance:edit']"
size="mini"
type="text"
icon="el-icon-close"
style="color: #E6A23C;"
@click="handleRosterMarkAbsence(scope.row)"
>缺勤</el-button>
<el-button
v-if="canRosterSignOut(scope.row)"
v-hasPermi="['train:attendance:checkin']"
size="mini"
type="text"
icon="el-icon-time"
@click="handleRosterSignOut(scope.row)"
>签退</el-button>
<el-button
v-if="canRosterAdjust(scope.row)"
v-hasPermi="['train:attendance:edit']"
size="mini"
type="text"
icon="el-icon-edit"
@click="handleRosterAdjust(scope.row)"
>调整</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="attendanceDialog.open = false">关 闭</el-button>
</div>
</el-dialog>
</div>
</template>
@@ -360,6 +594,13 @@ import {
getSchedulingOptions
} from '@/api/train/timetable'
import { listPlanCourses } from '@/api/train/planCourse'
import {
getLessonRoster,
signInAttendance,
signOutAttendance,
adjustAttendance,
markAbsenceAttendance
} from '@/api/train/attendance'
export default {
name: 'TimetablePanel',
@@ -376,7 +617,13 @@ export default {
data() {
return {
loading: false,
viewMode: 'timeline', // 'timeline' 或 'table'
viewMode: 'week', // 默认优先展示学校周课表视图,亦可切换 'timeline' 或 'table'
currentWeekAnchor: new Date(), // 当前周所锚定的日期对象
periodList: [
{ key: 'morning', label: '上午', timeRange: '08:00 - 12:00' },
{ key: 'afternoon', label: '下午', timeRange: '14:00 - 18:00' },
{ key: 'evening', label: '晚上', timeRange: '18:30 - 21:30' }
],
timetableData: {
planId: null,
planName: '',
@@ -418,7 +665,14 @@ export default {
},
// 冲突检测状态与结果
conflictChecking: false,
conflictResult: null
conflictResult: null,
// 课次考勤名册对话框
attendanceDialog: {
open: false,
loading: false,
lesson: {},
rosterList: []
}
}
},
computed: {
@@ -434,6 +688,47 @@ export default {
}
})
return list
},
// 当前锚定周的周一到周日 7 天日期清单
currentWeekDays() {
const anchor = this.currentWeekAnchor ? new Date(this.currentWeekAnchor) : new Date()
// JS中 getDay() 0为周日,1为周一...6为周六
const day = anchor.getDay()
const diffToMonday = day === 0 ? -6 : 1 - day
const monday = new Date(anchor)
monday.setDate(anchor.getDate() + diffToMonday)
const labels = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
const days = []
for (let i = 0; i < 7; i++) {
const d = new Date(monday)
d.setDate(monday.getDate() + i)
const year = d.getFullYear()
const month = String(d.getMonth() + 1).padStart(2, '0')
const date = String(d.getDate()).padStart(2, '0')
const dateStr = `${year}-${month}-${date}`
days.push({
dateStr,
displayDate: `${month}-${date}`,
label: labels[i],
dayOfWeek: i + 1
})
}
return days
},
currentWeekDisplayTitle() {
if (!this.currentWeekDays || this.currentWeekDays.length === 0) return ''
const first = this.currentWeekDays[0].dateStr
const last = this.currentWeekDays[6].dateStr
return `${first} 至 ${last}`
},
currentWeekLessonCount() {
if (!this.currentWeekDays || !this.allLessons) return 0
const dates = new Set(this.currentWeekDays.map(d => d.dateStr))
return this.allLessons.filter(l => {
const d = (l.startTime || '').substring(0, 10)
return dates.has(d)
}).length
}
},
watch: {
@@ -699,6 +994,191 @@ export default {
this.$modal.msgSuccess('删除成功')
this.loadTimetable()
}).catch(() => {})
},
// ---- 学校周课表方法 (UC-11) ----
handlePrevWeek() {
const anchor = this.currentWeekAnchor ? new Date(this.currentWeekAnchor) : new Date()
anchor.setDate(anchor.getDate() - 7)
this.currentWeekAnchor = anchor
},
handleNextWeek() {
const anchor = this.currentWeekAnchor ? new Date(this.currentWeekAnchor) : new Date()
anchor.setDate(anchor.getDate() + 7)
this.currentWeekAnchor = anchor
},
handleCurrentOrFirstWeek() {
if (this.timetableData.startDate) {
const parsed = new Date(this.timetableData.startDate.replace(/-/g, '/'))
if (!isNaN(parsed.getTime())) {
this.currentWeekAnchor = parsed
return
}
}
this.currentWeekAnchor = new Date()
},
isToday(dateStr) {
if (!dateStr) return false
const now = new Date()
const y = now.getFullYear()
const m = String(now.getMonth() + 1).padStart(2, '0')
const d = String(now.getDate()).padStart(2, '0')
return dateStr === `${y}-${m}-${d}`
},
getLessonsInCell(dateStr, periodKey) {
if (!this.allLessons || !dateStr) return []
return this.allLessons.filter(lesson => {
const lessonDate = (lesson.startTime || '').substring(0, 10)
if (lessonDate !== dateStr) return false
const timePart = (lesson.startTime || '').split(' ')[1] || ''
const hour = parseInt(timePart.split(':')[0], 10)
if (isNaN(hour)) return false
if (periodKey === 'morning') return hour < 13
if (periodKey === 'afternoon') return hour >= 13 && hour < 18
if (periodKey === 'evening') return hour >= 18
return false
})
},
handleQuickAddLesson(dateStr, periodKey) {
this.dialog.isEdit = false
this.dialog.title = `新增课次排课 (${dateStr})`
this.resetLessonForm()
let startH = '09:00:00'
let endH = '11:30:00'
if (periodKey === 'afternoon') {
startH = '14:00:00'
endH = '16:30:00'
} else if (periodKey === 'evening') {
startH = '19:00:00'
endH = '21:00:00'
}
this.lessonForm.startTime = `${dateStr} ${startH}`
this.lessonForm.endTime = `${dateStr} ${endH}`
this.dialog.open = true
this.conflictResult = null
},
// 支持从“本期课程”外部直接调用排课并预选课程
openAddLessonForCourse(planCourseId) {
this.dialog.isEdit = false
this.dialog.title = '新增课次排课'
this.resetLessonForm()
this.lessonForm.planCourseId = planCourseId
this.dialog.open = true
this.conflictResult = null
},
// ---- 课次学员考勤名册与签到/缺勤 (第四优先级) ----
handleOpenAttendance(lesson) {
this.attendanceDialog.lesson = lesson || {}
this.attendanceDialog.open = true
this.refreshAttendanceRoster()
},
refreshAttendanceRoster() {
if (!this.attendanceDialog.lesson || !this.attendanceDialog.lesson.lessonId) return
this.attendanceDialog.loading = true
getLessonRoster(this.attendanceDialog.lesson.lessonId)
.then(res => {
this.attendanceDialog.rosterList = res.data || []
})
.finally(() => {
this.attendanceDialog.loading = false
})
},
goToFullCheckinPage() {
this.attendanceDialog.open = false
this.$router.push({
path: '/train/checkin',
query: {
planId: this.planId,
lessonId: this.attendanceDialog.lesson.lessonId
}
})
},
formatEnrollmentStatusTag(status) {
const map = {
0: { text: '待报到', type: 'info' },
1: { text: '已报到', type: 'success' },
2: { text: '请假', type: 'warning' },
3: { text: '放弃', type: 'danger' },
4: { text: '已完成', type: 'primary' }
}
return map[status] || { text: '未知', type: 'info' }
},
formatAttendanceStatusTag(status) {
const map = {
0: { text: '缺勤', type: 'danger' },
1: { text: '正常', type: 'success' },
2: { text: '迟到', type: 'warning' },
3: { text: '早退', type: 'warning' },
4: { text: '请假', type: 'info' }
}
if (status == null) {
return { text: '未签到', type: 'info' }
}
return map[status] || { text: '已记录', type: 'info' }
},
canRosterSignIn(row) {
return row.enrollmentStatus === 1 && (row.attendanceStatus == null || row.attendanceStatus === 0)
},
canRosterMarkAbsence(row) {
return row.attendanceStatus !== 0
},
canRosterSignOut(row) {
return (row.attendanceStatus === 1 || row.attendanceStatus === 2) && !row.signOutTime
},
canRosterAdjust(row) {
return row.checkinId != null
},
handleRosterSignIn(row) {
signInAttendance({
lessonId: this.attendanceDialog.lesson.lessonId,
studentId: row.studentId,
checkinWay: 4
}).then(() => {
this.$modal.msgSuccess(`学员【${row.studentName}】人工签到成功`)
this.refreshAttendanceRoster()
})
},
handleRosterSignOut(row) {
signOutAttendance({
lessonId: this.attendanceDialog.lesson.lessonId,
studentId: row.studentId,
checkinWay: 4
}).then(() => {
this.$modal.msgSuccess(`学员【${row.studentName}】签退成功`)
this.refreshAttendanceRoster()
})
},
handleRosterMarkAbsence(row) {
this.$prompt(`请输入学员【${row.studentName}】的缺勤原因:`, '课次缺勤登记', {
confirmButtonText: '确定登记',
cancelButtonText: '取消',
inputPattern: /\S+/,
inputErrorMessage: '缺勤原因不能为空'
}).then(({ value }) => {
return markAbsenceAttendance({
lessonId: this.attendanceDialog.lesson.lessonId,
enrollmentId: row.enrollmentId,
remark: value
})
}).then(() => {
this.$modal.msgSuccess(`已登记学员【${row.studentName}】缺勤`)
this.refreshAttendanceRoster()
}).catch(() => {})
},
handleRosterAdjust(row) {
this.$prompt(`请输入调整后的考勤状态(1:正常 2:迟到 3:早退 4:请假 0:缺勤):`, '调整考勤状态', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPattern: /^[0-4]$/,
inputErrorMessage: '请输入0-4之间的数字状态码'
}).then(({ value }) => {
return adjustAttendance(row.checkinId, {
attendanceStatus: parseInt(value, 10),
remark: '管理员人工调整'
})
}).then(() => {
this.$modal.msgSuccess('考勤调整成功')
this.refreshAttendanceRoster()
}).catch(() => {})
}
}
}
@@ -711,6 +1191,159 @@ export default {
.empty-tip {
padding: 40px 0;
}
.school-timetable-view {
margin-top: 5px;
}
.week-nav-bar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
background: #fdfdfd;
padding: 8px 12px;
border: 1px solid #ebeef5;
border-radius: 4px;
}
.week-title-display {
font-weight: bold;
font-size: 14px;
color: #303133;
}
.school-grid-container {
overflow-x: auto;
border: 1px solid #ebeef5;
border-radius: 6px;
background: #fff;
}
.school-grid-table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
}
.school-grid-table th,
.school-grid-table td {
border: 1px solid #ebeef5;
padding: 6px;
vertical-align: top;
}
.time-col-header {
width: 100px;
background-color: #fafafa;
text-align: center;
font-size: 13px;
color: #606266;
font-weight: 600;
}
.day-col-header {
text-align: center;
background-color: #fafafa;
color: #303133;
padding: 8px 4px;
}
.day-col-header.is-today {
background-color: #ecf5ff;
border-top: 3px solid #409eff;
}
.header-day-label {
font-weight: bold;
font-size: 14px;
margin-bottom: 2px;
}
.header-date-label {
font-size: 12px;
color: #909399;
}
.period-title-cell {
background-color: #fafafa;
text-align: center;
vertical-align: middle !important;
}
.period-name {
font-weight: bold;
font-size: 14px;
color: #303133;
margin-bottom: 4px;
}
.period-time-range {
font-size: 11px;
color: #909399;
}
.timetable-cell {
min-height: 120px;
height: 135px;
position: relative;
background: #fff;
}
.school-lesson-card {
background: linear-gradient(135deg, #f0f7ff 0%, #e6f1fc 100%);
border: 1px solid #b3d8ff;
border-left: 4px solid #409eff;
border-radius: 4px;
padding: 6px 8px;
margin-bottom: 6px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
transition: all 0.2s;
}
.school-lesson-card:hover {
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.25);
transform: translateY(-1px);
}
.slc-time-tag {
font-size: 11px;
color: #409eff;
font-weight: 600;
margin-bottom: 3px;
}
.slc-course-title {
font-weight: bold;
font-size: 13px;
color: #1f2f3d;
margin-bottom: 4px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.slc-meta {
font-size: 11px;
color: #606266;
display: flex;
flex-direction: column;
gap: 2px;
}
.meta-row {
display: inline-flex;
align-items: center;
gap: 4px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.slc-card-actions {
margin-top: 5px;
padding-top: 3px;
border-top: 1px dashed #dcdfe6;
display: flex;
justify-content: flex-end;
gap: 8px;
}
.empty-cell-placeholder {
display: none;
height: 100%;
min-height: 80px;
align-items: center;
justify-content: center;
color: #c0c4cc;
font-size: 12px;
cursor: pointer;
border-radius: 4px;
border: 1px dashed #dcdfe6;
margin: 4px;
}
.timetable-cell:hover .empty-cell-placeholder {
display: flex;
background: #f9f9f9;
color: #409eff;
}
.day-cards {
display: flex;
flex-direction: column;
@@ -734,7 +1367,7 @@ export default {
align-items: center;
padding: 10px 14px;
background-color: #f8f9fa;
border-left: 4px solid #409EFF;
border-left: 4px solid #409eff;
border-radius: 4px;
}
.lesson-time {
+34 -2
View File
@@ -77,7 +77,7 @@
>{{ scope.row.enrolledCount || 0 }}</el-button>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="290" fixed="right">
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="340" fixed="right">
<template slot-scope="scope">
<el-button
v-hasPermi="['train:plan:query']"
@@ -93,6 +93,13 @@
icon="el-icon-reading"
@click="handleCourseConfig(scope.row)"
>课程配置</el-button>
<el-button
v-hasPermi="['train:attendance:list']"
size="mini"
type="text"
icon="el-icon-date"
@click="handleGoCheckin(scope.row)"
>考勤</el-button>
<el-button
v-hasPermi="['train:plan:edit']"
size="mini"
@@ -317,8 +324,16 @@
<el-table-column label="学时" prop="cDuration" width="70" align="center" />
<el-table-column label="排序号" prop="sortNo" width="70" align="center" />
<el-table-column label="配置说明" prop="remark" min-width="120" show-overflow-tooltip />
<el-table-column label="操作" align="center" width="80" fixed="right">
<el-table-column label="操作" align="center" width="140" fixed="right">
<template slot-scope="scope">
<el-button
v-hasPermi="['train:lesson:add']"
size="mini"
type="text"
icon="el-icon-date"
:disabled="isLocked(planDetailDialog.plan.planStatus)"
@click="handleGoScheduleCourse(scope.row)"
>去排课</el-button>
<el-button
v-hasPermi="['train:plan:edit']"
size="mini"
@@ -346,6 +361,7 @@
<el-tab-pane label="课程表" name="timetable">
<timetable-panel
v-if="planDetailDialog.open && planDetailDialog.plan.planId"
ref="timetablePanel"
:plan-id="planDetailDialog.plan.planId"
:plan-status="planDetailDialog.plan.planStatus"
/>
@@ -625,6 +641,22 @@ export default {
this.courseDialog.planName = row.planName
this.courseDialog.planStatus = row.planStatus
this.loadPlanCourses(row.planId)
},
// 跳转考勤管理中心并预选指定培训班
handleGoCheckin(row) {
this.$router.push({
path: '/train/checkin',
query: { planId: row.planId }
})
},
// 从本期课程列表中快捷前往课程表Tab并直接打开排课对话框
handleGoScheduleCourse(courseRow) {
this.planDetailDialog.activeTab = 'timetable'
this.$nextTick(() => {
if (this.$refs.timetablePanel && this.$refs.timetablePanel.openAddLessonForCourse) {
this.$refs.timetablePanel.openAddLessonForCourse(courseRow.planCourseId)
}
})
}
}
}