Compare commits

...

44 Commits

Author SHA1 Message Date
zhaichao 215a48446d 表格横向滚动优化 2026-08-24 11:02:04 +08:00
zhaichao 3ac8c9496e 首页 2026-08-24 10:51:17 +08:00
zhaichao a5cd5eab44 样式 2026-08-24 10:49:27 +08:00
zhaichao 5e3217dc8f 课表接口修改 2026-08-24 10:07:50 +08:00
zhaichao ee25e06680 学期管理页面样式优化 2026-08-24 10:07:36 +08:00
zhaichao 137528beb2 校历 2026-08-24 10:04:59 +08:00
zhaichao a29c357ed2 选修课页面恢复 2026-08-24 09:30:26 +08:00
zhaichao c2098878b1 课程科目 2026-08-24 09:20:20 +08:00
zhaichao 08b1ea4606 样式调整 2026-08-24 09:15:34 +08:00
zhaichao 43f1aab297 样式调整 2026-08-23 21:43:03 +08:00
zhaichao 71257bdce0 多余组件删除 2026-08-23 21:02:32 +08:00
zhaichao 526b9082df 班次学期信息管理 2026-08-23 21:02:15 +08:00
zhaichao 499518a2a6 分组教学任务和教学任务计划管理 2026-08-23 20:43:24 +08:00
zhaichao 18bbfd71ba 人才培养方案 2026-08-23 20:34:08 +08:00
zhaichao ec448a1977 教学大纲 2026-08-23 20:33:20 +08:00
zhaichao 0a25dac2c1 教学场地 2026-08-23 20:31:48 +08:00
zhaichao a8aba532e2 分组教学任务 2026-08-23 20:31:14 +08:00
zhaichao 8bf726a745 课表调整申请 2026-08-23 20:24:04 +08:00
zhaichao 26fe8ea334 冲突检查 2026-08-23 20:23:37 +08:00
zhaichao 5fb95ae476 课表调整 2026-08-23 20:13:46 +08:00
zhaichao 2d3150c566 课表 2026-08-23 19:54:30 +08:00
zhaichao 0c3de2809e 教员管理 2026-08-23 18:42:33 +08:00
zhaichao 24ca73ffa5 教材管理 2026-08-23 18:39:34 +08:00
zhaichao 24907a7621 班次学员队 2026-08-23 17:51:51 +08:00
zhaichao 4b952280b3 学员预警结果 2026-08-23 17:51:20 +08:00
zhaichao 12214a326f 学员学籍异动申请 2026-08-23 17:23:17 +08:00
zhaichao c7f334ed53 防抖优化 2026-08-23 17:15:24 +08:00
zhaichao 088a35c9e8 缩放优化 2026-08-23 16:58:51 +08:00
zhaichao 18f1faba5f 自适应优化 2026-08-23 16:48:29 +08:00
zhaichao 0ea410ffec 移除班次课程表占位页面 2026-08-23 16:33:31 +08:00
zhaichao 29387b3193 课时补助核算 2026-08-23 16:33:10 +08:00
zhaichao 001a264f16 方案管理 2026-08-23 16:32:47 +08:00
zhaichao 1abadfb3ff 居中 2026-08-23 16:32:25 +08:00
zhaichao cc623d5fdb 全局表格字段居中显示 2026-08-23 16:31:38 +08:00
zhaichao 370cb615d0 课时补助核算两个tab页 2026-08-23 16:20:07 +08:00
zhaichao 97562579dc 联教联训管理接口文件 2026-08-23 16:17:51 +08:00
zhaichao 7a453e4ac3 联教联训管理 2026-08-23 16:17:28 +08:00
zhaichao a4b3c241a2 点击标题关闭所有tab 2026-08-23 16:12:57 +08:00
zhaichao fe008ce383 班次课程表二级菜单加图标以及添加占位页面 2026-08-23 16:09:46 +08:00
zhaichao 657774e1b1 禁止修改全局布局,以及菜单栏变动 2026-08-23 16:08:48 +08:00
zhaichao 4efb84e5d2 教学实施计划导入 2026-08-23 16:03:02 +08:00
zhaichao 776ffaac4f 教学日志管理 2026-08-23 15:57:09 +08:00
zhaichao 8a8aa0c7ca 学科专业 2026-08-23 15:47:43 +08:00
zhaichao 61ef0c1214 全局自适应以及表格横向拖动优化 2026-08-23 15:36:11 +08:00
86 changed files with 11490 additions and 9324 deletions
+2 -11
View File
@@ -1,20 +1,11 @@
<template>
<template>
<div id="app">
<router-view />
<theme-picker />
</div>
</template>
<script>
import ThemePicker from "@/components/ThemePicker"
export default {
name: "App",
components: { ThemePicker }
name: "App"
}
</script>
<style scoped>
#app .theme-picker {
display: none;
}
</style>
+42
View File
@@ -0,0 +1,42 @@
import request from '@/utils/request'
// ======================== 课时补助核算:课时统计 / 课时费统计 ========================
// 接口前缀 /ks,经 vue.config.js 代理转发为 /api/ks;返回行为 HourStatisticsVO。
// 分页查询课时统计(支持 nd 年度、xq 学期序号、jyxm 教员姓名)
export function listHourStatistics(query) {
return request({
url: '/ks/hour-stat/list',
method: 'get',
params: query
})
}
// 导出课时统计 Excel(返回 Blob,文件名 课时统计.xlsx)
export function exportHourStatistics(query) {
return request({
url: '/ks/hour-stat/export',
method: 'get',
params: query,
responseType: 'blob'
})
}
// 分页查询课时费统计(支持 nd、xq、jyxm、ksfbzbh 课时费标准编号)
export function listFeeStatistics(query) {
return request({
url: '/ks/fee-stat/list',
method: 'get',
params: query
})
}
// 导出课时费统计 Excel(返回 Blob,文件名 课时费统计.xlsx)
export function exportFeeStatistics(query) {
return request({
url: '/ks/fee-stat/export',
method: 'get',
params: query,
responseType: 'blob'
})
}
@@ -0,0 +1,69 @@
import request from '@/utils/request'
// ======================== 联教联训管理(课时补助项目) ========================
// 接口前缀 /ks,经 vue.config.js 代理转发为 /api/ks;后端实体为 KSBZXM。
// 新增联教联训(编号为空时后端自动生成 UUID)
export function addJointTraining(data) {
return request({
url: '/ks/yjlx/add',
method: 'post',
data: data
})
}
// 删除联教联训
export function deleteJointTraining(bh) {
return request({
url: '/ks/yjlx/delete',
method: 'post',
params: { bh: bh }
})
}
// 修改联教联训
export function updateJointTraining(data) {
return request({
url: '/ks/yjlx/update',
method: 'post',
data: data
})
}
// 按编号查询联教联训详情
export function getJointTraining(bh) {
return request({
url: '/ks/yjlx/get',
method: 'get',
params: { bh: bh }
})
}
// 分页查询联教联训列表(支持名称模糊)
export function listJointTraining(query) {
return request({
url: '/ks/yjlx/list',
method: 'get',
params: query
})
}
// 导入联教联训 Excel(multipart,字段名 file)
export function importJointTraining(file) {
const formData = new FormData()
formData.append('file', file)
return request({
url: '/ks/yjlx/import',
method: 'post',
data: formData
})
}
// 导出联教联训到 Excel(返回 Blob)
export function exportJointTraining() {
return request({
url: '/ks/yjlx/export',
method: 'get',
responseType: 'blob'
})
}
+87
View File
@@ -0,0 +1,87 @@
import request from '@/utils/request'
// ======================== 方案管理:课时费方案 / 课时方案 ========================
// 接口前缀 /ks,经 vue.config.js 代理转发为 /api/ks。
// ---------- 课时费方案(KSFBZ) ----------
// 分页列表(名称模糊)
export function listFeeStandard(query) {
return request({
url: '/ks/fee-standard/list',
method: 'get',
params: query
})
}
// 详情
export function getFeeStandard(bh) {
return request({
url: '/ks/fee-standard/get',
method: 'get',
params: { bh: bh }
})
}
// 新增(编号为空时后端自动生成 UUID)
export function addFeeStandard(data) {
return request({
url: '/ks/fee-standard/add',
method: 'post',
data: data
})
}
// 编辑(编号必传)
export function updateFeeStandard(data) {
return request({
url: '/ks/fee-standard/update',
method: 'post',
data: data
})
}
// 删除
export function deleteFeeStandard(bh) {
return request({
url: '/ks/fee-standard/delete',
method: 'post',
params: { bh: bh }
})
}
// ---------- 课时方案(KSXSFA) ----------
// 分页列表(名称模糊)
export function listCoefficientPlan(query) {
return request({
url: '/ks/coefficient-plan/list',
method: 'get',
params: query
})
}
// 详情
export function getCoefficientPlan(bh) {
return request({
url: '/ks/coefficient-plan/get',
method: 'get',
params: { bh: bh }
})
}
// 新增(编号为空时后端自动生成 UUID)
export function addCoefficientPlan(data) {
return request({
url: '/ks/coefficient-plan/add',
method: 'post',
data: data
})
}
// 编辑(编号必传)
export function updateCoefficientPlan(data) {
return request({
url: '/ks/coefficient-plan/update',
method: 'post',
data: data
})
}
+39 -43
View File
@@ -1,10 +1,9 @@
import request from '@/utils/request'
// ======================== 教学日志核心 ========================
// 说明:以下接口均已与实际运行的后端 LogController 逐一核对(前缀 /log,
// 经 vue.config.js 代理转发为 /api/log)。axios baseURL 为 /api(.env.development)。
// ======================== 教学日志核心(以 api.txt 为准) ========================
// 接口前缀 /log,经 vue.config.js 代理转发为 /api/log(axios baseURL 为 /api)。
// 分页条件查询教学日志列表(待上报 / 待审核视图)
// 分页条件查询教学日志列表(待上报视图)
// 支持参数:pageNum pageSize ksrq jsrq kcmc jys jxff skjy rzzt pxqb gdqk gdxxqk rwlb
export function getTeachingLogList(query) {
return request({
@@ -32,7 +31,7 @@ export function getAuditedTeachingLogList(query) {
})
}
// 新增教学日志
// 新增教学日志(请求体为 SSKCB_RZ 实体)
export function addTeachingLog(data) {
return request({
url: '/log/teaching-log/add',
@@ -41,21 +40,39 @@ export function addTeachingLog(data) {
})
}
// 批量上报教学日志(入参为 bh 编号数组,Content-Type: application/json)
export function batchReportTeachingLog(bhList) {
// 编辑教学日志(请求体为 SSKCB_RZ 实体,bh 必传)
export function updateTeachingLog(data) {
return request({
url: '/log/teaching-log/batch-report',
url: '/log/teaching-log/update',
method: 'post',
data: bhList
data: data
})
}
// 批量审核教学日志(入参为 bh 编号数组)
export function batchAuditTeachingLog(bhList) {
// 根据编号查询教学日志详情
export function getTeachingLogByBh(bh) {
return request({
url: '/log/teaching-log/batch-audit',
url: '/log/teaching-log/get',
method: 'get',
params: { bh: bh }
})
}
// 提交教学日志审核,接口形态:POST /log/teaching-log/submit?bh=&targetStatus=
export function submitTeachingLog(bh, targetStatus) {
return request({
url: '/log/teaching-log/submit',
method: 'post',
data: bhList
params: { bh: bh, targetStatus: targetStatus }
})
}
// 审核通过教学日志,接口形态:GET /log/teaching-log/approve?bh=&feedback=
export function approveTeachingLog(bh, feedback) {
return request({
url: '/log/teaching-log/approve',
method: 'get',
params: { bh: bh, feedback: feedback }
})
}
@@ -68,6 +85,15 @@ export function rejectTeachingLog(bh, thyj) {
})
}
// 根据课表自动创建教学日志,接口形态:POST /log/teaching-log/auto-create?tybh=
export function autoCreateTeachingLog(tybh) {
return request({
url: '/log/teaching-log/auto-create',
method: 'post',
params: { tybh: tybh }
})
}
// 批量导入教学日志 Excel,接口形态:POST /log/teaching-log/import-excel(multipart,字段名 file)
export function importTeachingLogExcel(file) {
const formData = new FormData()
@@ -97,33 +123,3 @@ export function downloadTeachingLogTemplate() {
responseType: 'blob'
})
}
// ======================== TODO:后端暂未提供,保留占位待接 ========================
// TODO(后端缺接口):批量删除教学日志 —— 当前后端 LogController 无 delete 接口,
// 待后端提供后确认入参与请求方式。
export function deleteTeachingLog(bhList) {
return request({
url: '/log/teaching-log/delete',
method: 'post',
data: bhList
})
}
// TODO(后端缺接口):重新检查通报变更情况 —— 当前后端无 recheck 接口,待后端提供后确认。
export function recheckTeachingLog() {
return request({
url: '/log/teaching-log/recheck',
method: 'post'
})
}
// TODO(后端缺接口):根据编号查询教学日志详情 —— 当前后端无 teaching-log/get 接口。
// 列表接口已含完整字段,组件以行数据兜底展示;待后端提供 get 接口后再联调。
export function getTeachingLogByBh(bh) {
return request({
url: '/log/teaching-log/get',
method: 'get',
params: { bh: bh }
})
}
+34
View File
@@ -0,0 +1,34 @@
import request from '@/utils/request'
// ======================== 教学实施计划导入 ========================
// 接口前缀 /teachingPlan,经 vue.config.js 代理转发为 /api/teachingPlan。
// 分页查询教学实施计划列表
// 支持参数:pageNum pageSize nd kcmc bc zrdw skjy ytgjc ybdr(与后端 Mapper 一致)
export function listTeachingPlan(query) {
return request({
url: '/teachingPlan/list',
method: 'get',
params: query
})
}
// 导入教学实施计划 Excel(multipart,字段名 file),返回导入条数
export function importTeachingPlan(file) {
const formData = new FormData()
formData.append('file', file)
return request({
url: '/teachingPlan/import',
method: 'post',
data: formData
})
}
// 下载教学实施计划导入模板(返回 Blob)
export function downloadTeachingPlanTemplate() {
return request({
url: '/teachingPlan/template',
method: 'get',
responseType: 'blob'
})
}
@@ -0,0 +1,49 @@
import request from '@/utils/request'
// ======================== 学员学籍异动申请(实体 XYXJYDSQB) ========================
// 列表查询仅支持 bh / xybh / sqlx / zt 四个等值筛选。
// 分页查询学籍异动申请列表
export function listApplication(query) {
return request({
url: '/student-records/application/list',
method: 'get',
params: query
})
}
// 查询学籍异动申请详情(bh 异动申请编号)
export function getApplication(bh) {
return request({
url: '/student-records/application/get',
method: 'get',
params: { bh: bh }
})
}
// 新增学籍异动申请(请求体 XYXJYDSQB 实体;bh 需前端传入,创建/修改时间后端自动维护)
export function addApplication(data) {
return request({
url: '/student-records/application/add',
method: 'post',
data: data
})
}
// 编辑学籍异动申请(bh 异动申请编号必传,请求体 XYXJYDSQB 实体)
export function updateApplication(data) {
return request({
url: '/student-records/application/update',
method: 'post',
data: data
})
}
// 删除学籍异动申请(bh 异动申请编号)
export function delApplication(bh) {
return request({
url: '/student-records/application/delete',
method: 'post',
params: { bh: bh }
})
}
@@ -0,0 +1,105 @@
import request from '@/utils/request'
// ======================== 班历管理(实体 XYDRWB 学员队任务表) ========================
// 接口依据:api2.txt「班次学期日历(班历)」分组 + StudentTeamTaskController
// 每个班次学期(xydxqbh)下挂若干条课程任务记录:
// bh 编号、nd 年度、xydbh 学员队编号、kbh 课编号、xqdc 学期第次、jybh 教员编号、kcxh 课次序号、
// jsbh 教室编号、rs 人数、zks 周课时、xs 学时、klx 课类型、jc 简称、ksdd 考试地点、ksbh 考试编号、
// jysdh 教研室代号、jysjhjybh 教研室计划教员编号、jysjhbz 教研室计划备注、jhkcxh 计划课次序号、
// xhbs 序号标识、xydxqbh 学员队学期编号、bz 备注、xf 学分、cjsj/bdsj/kbbdsj 时间、
// ksks 考试课时、bz2 编组、cjfz 成绩分制、bjrxypjf 不计入学员平均分、llxs 理论学时、sjxs 实践学时、
// ksksbxs 考试课时不显示、pdxh 配档序号、pdqsz 配档起始周、pdaz 配档按周、pdzzksj 配档占正课时间、
// pdbz 配档编组、pdtbykxxbz 配当同班异课选修编组、pdzdyxsfbsj 配档自定义学时分布数据、
// pdqyzdyxs 配档启用自定义学时
// 查询某班次学期下的全部班历记录(xydxqbh 学员队学期编号)
export function listClassCalendar(xydxqbh) {
return request({
url: '/classCalendar/all',
method: 'get',
params: { xydxqbh: xydxqbh }
})
}
// 根据主键查询班历记录
export function getClassCalendar(bh) {
return request({
url: '/classCalendar/get',
method: 'get',
params: { bh: bh }
})
}
// 更新班历记录(请求体 XYDRWB,bh 必传)
export function updateClassCalendar(data) {
return request({
url: '/classCalendar/update',
method: 'post',
data: data
})
}
// 选定区域设置应用于其它班次(请求体 { sourceBhList: 源记录编号, targetBhList: 目标班次学期编号 })
export function batchCopyCalendar(data) {
return request({
url: '/classCalendar/batchCopy',
method: 'post',
data: data
})
}
// 整个班历应用于其它班次(请求体 { sourceXydxqbh: 源班次学期编号, targetBhList: 目标班次学期编号 })
export function batchCopyCalendarBySemester(data) {
return request({
url: '/classCalendar/batchCopyBySemester',
method: 'post',
data: data
})
}
// ======================== 学员队任务表操作(/studentTeamTask) ========================
// 新增班历记录(请求体 XYDRWB;bh 留空后端生成 UUID)
export function addTeamTask(data) {
return request({
url: '/studentTeamTask/add',
method: 'post',
data: data
})
}
// 删除班历记录(bh 编号)
export function delTeamTask(bh) {
return request({
url: '/studentTeamTask/delete',
method: 'post',
params: { bh: bh }
})
}
// 批量预设合班(请求体 { bhList },按年度排序生成统一课次序号)
export function batchPresetMerge(bhList) {
return request({
url: '/studentTeamTask/batchPresetMerge',
method: 'post',
data: { bhList: bhList }
})
}
// 批量预设拆班(请求体 { bhList },清空课次序号)
export function batchPresetSplit(bhList) {
return request({
url: '/studentTeamTask/batchPresetSplit',
method: 'post',
data: { bhList: bhList }
})
}
// 自动生成必修课程(按学员队+学期第次从专业教学计划生成班历记录)
export function autoGenerateRequiredCourses(xydbh, xqdc) {
return request({
url: '/studentTeamTask/autoGenerateRequiredCourses',
method: 'post',
params: { xydbh: xydbh, xqdc: xqdc }
})
}
+117
View File
@@ -0,0 +1,117 @@
import request from '@/utils/request'
// ======================== 班次学期管理(实体 XYDNDXQJBXXB 学员队年度学期基本信息表) ========================
// 接口依据:api1.txt「班次学期」分组,实际后端位于 StudentRecordsController (/student-records/semester/*)
// 列表仅支持 xydbh(学员队编号)模糊筛选,无年度筛选,前端需本地过滤
// 新增 bh 留空由后端生成 UUID;字段 nd 年度、xydbh 学员队编号、kxrq 开学日期、jsrq 结束日期、
// xqdc 学期第次、zyjsbh 专用教室编号、jxrwbh 教学任务编号、bdsj 变动时间、bz 备注、
// xhbs 序号标识、ysbbh 预设编班号、jclb 节次类别、xtms 系统模式、kfjypk 开放教员排课(0/1)、
// jsonzd、fxbgscsj 分析报告生成时间、fxbgwd1bh/fxbgwd2bh/fxbgwd3bh 分析报告文档编号
// 分页查询班次学期列表(支持 xydbh 模糊)
export function listSemester(query) {
return request({
url: '/student-records/semester/list',
method: 'get',
params: query
})
}
// 查询所有有效班次学期(DEL_FLAG = 0)
export function allSemester() {
return request({
url: '/student-records/semester/all',
method: 'get'
})
}
// 查询班次学期详情(bh 编号)
export function getSemester(bh) {
return request({
url: '/student-records/semester/get',
method: 'get',
params: { bh: bh }
})
}
// 新增班次学期(请求体 XYDNDXQJBXXB;bh 留空后端生成 UUID,delFlag 后端置 0)
export function addSemester(data) {
return request({
url: '/student-records/semester/add',
method: 'post',
data: data
})
}
// 编辑班次学期(bh 编号必传,请求体 XYDNDXQJBXXB)
export function updateSemester(data) {
return request({
url: '/student-records/semester/update',
method: 'post',
data: data
})
}
// 删除班次学期(逻辑删除并级联删除其班历记录)
export function delSemester(bh) {
return request({
url: '/student-records/semester/delete',
method: 'post',
params: { bh: bh }
})
}
// 批量删除班次学期(请求体为编号数组)
export function batchDeleteSemester(bhList) {
return request({
url: '/student-records/semester/batchDelete',
method: 'post',
data: bhList
})
}
// 批量设定学期日期(请求体 { bhList, xqkssj, xqjssj })
export function batchUpdateDateRange(data) {
return request({
url: '/student-records/semester/batchUpdateDateRange',
method: 'post',
data: data
})
}
// 批量修改开放教员排课状态(请求体 { bhList, kfjypk },0-否 1-是)
export function batchUpdateKfjypk(data) {
return request({
url: '/student-records/semester/batchUpdateKfjypk',
method: 'post',
data: data
})
}
// 批量修改学期第次(请求体 { bhList, xqdc })
export function batchUpdateXqdc(data) {
return request({
url: '/student-records/semester/batchUpdateXqdc',
method: 'post',
data: data
})
}
// 批量设置教学任务(请求体 { bhList, jxrwbh })
export function batchUpdateJxrwbh(data) {
return request({
url: '/student-records/semester/batchUpdateJxrwbh',
method: 'post',
data: data
})
}
// 批量创建班次学期(按时间范围筛选可建班的学员队;xqdc 传 春季学期/夏季学期/秋季学期)
// 参数:startTime 起始日、endTime 截止日、nd 年度、xqdc 学期
export function batchAddFromElective(params) {
return request({
url: '/student-records/semester/batchAddFromElective',
method: 'post',
params: params
})
}
+70
View File
@@ -0,0 +1,70 @@
import request from '@/utils/request'
// ======================== 班次(学员队)管理(实体 XYDB) ========================
// 分页列表查询条件为 XYDB 实体字段。
// 分页查询学员队列表
export function listTeam(query) {
return request({
url: '/student-records/team/list',
method: 'get',
params: query
})
}
// 查询学员队详情(xydbh 学员队编号)
export function getTeam(xydbh) {
return request({
url: '/student-records/team/get',
method: 'get',
params: { xydbh: xydbh }
})
}
// 新增学员队(请求体 XYDB)
export function addTeam(data) {
return request({
url: '/student-records/team/add',
method: 'post',
data: data
})
}
// 编辑学员队(xydbh 学员队编号必传,请求体 XYDB)
export function updateTeam(data) {
return request({
url: '/student-records/team/update',
method: 'post',
data: data
})
}
// 删除学员队(xydbh 学员队编号)
export function delTeam(xydbh) {
return request({
url: '/student-records/team/delete',
method: 'post',
params: { xydbh: xydbh }
})
}
// 导入学员队 Excel(multipart,字段名 file)
export function importTeam(file) {
const formData = new FormData()
formData.append('file', file)
return request({
url: '/student-records/team/import',
method: 'post',
data: formData
})
}
// 导出学员队 Excel(查询条件为 XYDB 实体)
export function exportTeam(query) {
return request({
url: '/student-records/team/export',
method: 'get',
params: query,
responseType: 'blob'
})
}
@@ -0,0 +1,21 @@
import request from '@/utils/request'
// ======================== 学员学籍预警结果(实体 XYXJYJGJG,查询:实体类) ========================
// 分页查询预警结果列表
export function listWarningResult(query) {
return request({
url: '/student-records/warning-result/list',
method: 'get',
params: query
})
}
// 查询预警结果详情(bh 预警结果编号)
export function getWarningResult(bh) {
return request({
url: '/student-records/warning-result/get',
method: 'get',
params: { bh: bh }
})
}
@@ -0,0 +1,46 @@
import request from '@/utils/request'
// 分页查询学科专业列表
export function listDiscipline(query) {
return request({
url: '/discipline/list',
method: 'get',
params: query
})
}
// 查询学科专业详情
export function getDiscipline(bsh) {
return request({
url: '/discipline/get',
method: 'get',
params: { bsh: bsh }
})
}
// 新增学科专业
export function addDiscipline(data) {
return request({
url: '/discipline/add',
method: 'post',
data: data
})
}
// 编辑学科专业
export function updateDiscipline(data) {
return request({
url: '/discipline/update',
method: 'post',
data: data
})
}
// 停用学科专业
export function disableDiscipline(bsh) {
return request({
url: '/discipline/disable',
method: 'post',
params: { bsh: bsh }
})
}
@@ -0,0 +1,95 @@
import request from '@/utils/request'
// 教学场地/教室管理(JSB 教室表 /classroom)
// 接口依据:classroom 控制器(前端 baseURL 为 /api,此处不重复 /api 前缀)
/**
* 新增教室
* POST /classroom/add
* 请求体(JSB):id/jsbh/jsmc 必填,其余字段可空;ty/xslx 为 Boolean
*/
export function addClassroom(data) {
return request({
url: '/classroom/add',
method: 'post',
data
})
}
/**
* 删除教室(不提供批量删除接口)
* POST /classroom/delete
* @param id 教室编号(主键,必填)
*/
export function deleteClassroom(id) {
return request({
url: '/classroom/delete',
method: 'post',
params: { id }
})
}
/**
* 更新教室
* POST /classroom/update
* 请求体(JSB):id 必传
*/
export function updateClassroom(data) {
return request({
url: '/classroom/update',
method: 'post',
data
})
}
/**
* 根据编号查询教室详情
* GET /classroom/get
* @param id 教室编号
*/
export function getClassroom(id) {
return request({
url: '/classroom/get',
method: 'get',
params: { id }
})
}
/**
* 分页查询教室列表
* GET /classroom/list
* 查询参数:jsbh(模糊)、jsmc(模糊)、jxldh(等值)、jxcdlx(等值)、ty(等值)、pageNum、pageSize
* 返回:PageResult<JSB>(records/total)
*/
export function listClassroom(params) {
return request({
url: '/classroom/list',
method: 'get',
params
})
}
/**
* 查询全部教室
* GET /classroom/all
* 返回:List<JSB>(无分页)
*/
export function listAllClassroom() {
return request({
url: '/classroom/all',
method: 'get'
})
}
/**
* 根据教学楼代号查询教室
* GET /classroom/listByJxldh
* @param jxldh 教学楼代号(必填)
*/
export function listClassroomByJxldh(jxldh) {
return request({
url: '/classroom/listByJxldh',
method: 'get',
params: { jxldh }
})
}
@@ -0,0 +1,166 @@
import request from '@/utils/request'
// 课程教学运行管理(CourseRunningController /course-running)
// 接口依据:course-running 控制器(前端 baseURL 为 /api,此处不重复 /api 前缀)
/**
* 分页查询教学实施计划课次列表
* GET /course-running/plan/list
* 查询参数(TeachingPlanQueryDTO):
* sskcbh 实施_课程编号、nd 年度、jxff 教学方法、
* rqStart 日期范围开始、rqEnd 日期范围结束(均传 ISO 格式,如 2026-08-23T00:00:00)、
* txzt 填写状态(0-未填写 1-已填写 2-已提交)、pageNum、pageSize
* 返回:PageResult<TeachingPlanLessonVO>(records/total)
*/
export function listTeachingPlan(params) {
return request({
url: '/course-running/plan/list',
method: 'get',
params
})
}
// ==================== 调课申请管理 /course-running/adjust ====================
// 接口依据:CourseRunningController(前端 baseURL 为 /api,此处不重复 /api 前缀)
/**
* 分页查询调课申请列表
* GET /adjust/list
* 查询参数(ScheduleAdjustQueryDTO):sskcbbh 课次编号、sqjybh 申请教员编号、nd 年度、
* jyspzzt 教研室审批状态、jyscszt 教研室查收状态、jgspzt 机关审批状态、sczt 删除状态、
* cjsjStart/cjsjEnd 申请日期范围(ISO 格式)、pageNum、pageSize
* 返回:PageResult<ScheduleAdjustApplyVO>(records/total)
*/
export function listScheduleAdjust(params) {
return request({
url: '/course-running/adjust/list',
method: 'get',
params
})
}
/**
* 查询可供调课课次
* GET /adjust/lessons
* 查询参数:nd 学期年度
* 返回:List<AdjustableLessonVO>
* 注意:后端当前未实现该接口,调用会报错(页面已做兜底提示)
*/
export function listAdjustableLessons(params) {
return request({
url: '/course-running/adjust/lessons',
method: 'get',
params
})
}
/**
* 查询调课申请详情
* GET /adjust/detail
* @param ssdksqbh 调课申请编号
* 返回:ScheduleAdjustDetailVO
*/
export function getScheduleAdjustDetail(ssdksqbh) {
return request({
url: '/course-running/adjust/detail',
method: 'get',
params: { ssdksqbh }
})
}
/**
* 提交调课申请
* POST /adjust/submit
* 请求体(ScheduleAdjustApplyDTO):sskcbbh 课次编号、sy 调课事由、rq 新日期、jc 新节次、
* nd 年度、sqjybh 申请教员编号、jxnr 教学内容、jxyd 教学要点、jxff 教学方法、
* jxbzbz 教学保障备注、ycxx 用车信息、bz 备注、roomList/teacherList/teamList/supportList
*/
export function submitScheduleAdjust(data) {
return request({
url: '/course-running/adjust/submit',
method: 'post',
data
})
}
/**
* 教研室审批调课申请
* POST /adjust/audit/jys
* 请求体(ScheduleAdjustAuditDTO):ssdksqbh、auditRole=jys、spzt(1同意/2发回/3拒绝)、fhyj、sprbh
*/
export function auditByTeachingOffice(data) {
return request({
url: '/course-running/adjust/audit/jys',
method: 'post',
data
})
}
/**
* 教研室查收调课申请
* POST /adjust/receive/jys
* @param ssdksqbh 调课申请编号
* @param csrbh 查收人编号
*/
export function receiveByTeachingOffice(params) {
return request({
url: '/course-running/adjust/receive/jys',
method: 'post',
params
})
}
/**
* 教学系审批调课申请
* POST /adjust/audit/jxx
* 请求体(ScheduleAdjustAuditDTO):ssdksqbh、auditRole=jxx、spzt、fhyj、sprbh
*/
export function auditByTeachingDepartment(data) {
return request({
url: '/course-running/adjust/audit/jxx',
method: 'post',
data
})
}
/**
* 机关审批调课申请
* POST /adjust/audit/jg
* 请求体(ScheduleAdjustAuditDTO):ssdksqbh、auditRole=jg、spzt、fhyj、sprbh、jgbh(机关编号必填)
*/
export function auditByAuthority(data) {
return request({
url: '/course-running/adjust/audit/jg',
method: 'post',
data
})
}
/**
* 机关查收调课申请
* POST /adjust/receive/jg
* @param ssdksqbh 调课申请编号
* @param jgbh 机关编号
* @param csrbh 查收人编号
*/
export function receiveByAuthority(params) {
return request({
url: '/course-running/adjust/receive/jg',
method: 'post',
params
})
}
/**
* 撤销调课申请
* DELETE /adjust/cancel
* @param ssdksqbh 调课申请编号
* @param sqjybh 申请教员编号(权限校验)
*/
export function cancelScheduleAdjust(params) {
return request({
url: '/course-running/adjust/cancel',
method: 'delete',
params
})
}
@@ -9,6 +9,14 @@ export function listSemester(query) {
})
}
// 查询所有学期列表(年度下拉数据源,返回全部学期含 nd/dqxq,按年度倒序)
export function listAllSemester() {
return request({
url: '/semester/all',
method: 'get'
})
}
// 根据主键查询学期
export function getSemester(nd) {
return request({
@@ -0,0 +1,18 @@
import request from '@/utils/request'
// 学员队任务表(StudentTeamTaskController /studentTeamTask)
// 接口依据:studentTeamTask 控制器(前端 baseURL 为 /api,此处不重复 /api 前缀)
/**
* 分页查询学员队任务表(按班次/年度)
* GET /studentTeamTask/list
* 查询参数(XYDRWB 实体字段):xydbh 学员队编号、nd 年度、kcxh 课次序号、jhkcxh 计划课次序号、xs 学时等
* 返回:Page<XYDRWB>(records/total)
*/
export function listStudentTeamTask(params) {
return request({
url: '/studentTeamTask/list',
method: 'get',
params
})
}
@@ -0,0 +1,66 @@
import request from '@/utils/request'
// 专业教学计划(教学大纲 ZYJXJHB)
// 接口依据:/zyjxjhb/*(前端 baseURL 为 '/api',此处不再加前缀)
// 新增(bh 留空后端自动生成 UUID,ty 默认 0,qysj 默认当前时间)
export function addSyllabus(data) {
return request({
url: '/zyjxjhb/add',
method: 'post',
data
})
}
// 删除(软删:置 ty=1 并写 tysj)
export function deleteSyllabus(bh) {
return request({
url: '/zyjxjhb/delete',
method: 'post',
params: { bh }
})
}
// 批量删除(软删)
export function batchDeleteSyllabus(bhList) {
return request({
url: '/zyjxjhb/batchDelete',
method: 'post',
data: bhList
})
}
// 更新(bh 必传)
export function updateSyllabus(data) {
return request({
url: '/zyjxjhb/update',
method: 'post',
data
})
}
// 根据编号查询
export function getSyllabus(bh) {
return request({
url: '/zyjxjhb/get',
method: 'get',
params: { bh }
})
}
// 查询所有(返回 List,无分页,前端本地分页)
export function listSyllabus() {
return request({
url: '/zyjxjhb/list',
method: 'get'
})
}
// 根据专业代号和停用标识查询(zydh、ty 均必填,等值匹配)
export function listSyllabusByZydhAndTy(zydh, ty) {
return request({
url: '/zyjxjhb/listByZydhAndTy',
method: 'get',
params: { zydh, ty }
})
}
@@ -0,0 +1,90 @@
import request from '@/utils/request'
// 教学任务管理(TeachingTaskController /teachingTask)
// 接口依据:api.txt「教学任务管理」分组(前端 baseURL 为 /api,此处不重复 /api 前缀)
// 实体:JXRW(bh 编号、rwmc 任务名称、nd 年度、zt 状态、
// fbsj 发布时间、cjsj 创建时间、jssj 结束时间、jcxqscsj 教材需求生成时间)
// 新增时 bh 留空由后端生成(api.txt 新增示例未传 bh),编辑/更新时 bh 必传
/**
* 分页查询教学任务列表
* GET /teachingTask/list
* 查询参数(JXRW):rwmc 任务名称(模糊)、nd 年度(等值)、zt 状态(等值)、
* pageNum、pageSize
* 返回:PageResult<JXRW>(records/total)
*/
export function listTeachingTask(params) {
return request({
url: '/teachingTask/list',
method: 'get',
params
})
}
/**
* 根据编号查询教学任务
* GET /teachingTask/get?bh=
* @param bh 编号
* 返回:JXRW
*/
export function getTeachingTask(bh) {
return request({
url: '/teachingTask/get',
method: 'get',
params: { bh }
})
}
/**
* 新增教学任务
* POST /teachingTask/add
* 请求体(JXRW):rwmc 任务名称(必填)、nd 年度(必填)、zt 状态、fbsj/jssj/jcxqscsj
* 说明:bh 不传由后端自动生成;cjsj 创建时间由后端自动维护
*/
export function addTeachingTask(data) {
return request({
url: '/teachingTask/add',
method: 'post',
data
})
}
/**
* 更新教学任务(根据主键)
* POST /teachingTask/update
* 请求体(JXRW):bh 编号(必填)、rwmc、nd、zt
*/
export function updateTeachingTask(data) {
return request({
url: '/teachingTask/update',
method: 'post',
data
})
}
/**
* 级联删除教学任务和教研室任务书
* POST /teachingTask/delete?bh=
* @param bh 编号
*/
export function deleteTeachingTask(bh) {
return request({
url: '/teachingTask/delete',
method: 'post',
params: { bh }
})
}
/**
* 教学任务发布(需要先填写教研室任务书)
* POST /teachingTask/batchPublish?bh=
* @param bh 教学任务编号
* 返回:更新数量
*/
export function publishTeachingTask(bh) {
return request({
url: '/teachingTask/batchPublish',
method: 'post',
params: { bh }
})
}
@@ -0,0 +1,81 @@
import request from '@/utils/request'
// 课表(课程表 KCB)管理(KCBController /kcb)
// 接口依据:KCBController + KCBServiceImpl
// 说明:KCB 实体主键为 bh(编号,IdType.INPUT);新增时后端强制 setBh(UUID) 覆盖、cjsj/bdsj 自动生成;
// delete/get/update 中的 URL 参数 kbh 实为 bh(编号)值;list 无分页返回全部记录
/**
* 新增课程表
* POST /kcb/add
* body 为 KCB 实体;bh 可留空(后端自动生成 UUID),cjsj/bdsj 后端自动维护
*/
export function addKcb(data) {
return request({
url: '/kcb/add',
method: 'post',
data
})
}
/**
* 删除课程表
* POST /kcb/delete?kbh= (kbh 实为 bh 编号值)
*/
export function deleteKcb(kbh) {
return request({
url: '/kcb/delete',
method: 'post',
params: { kbh }
})
}
/**
* 批量删除课程表
* POST /kcb/batchDelete
* body 为 bh(编号)字符串数组
*/
export function batchDeleteKcb(kbhList) {
return request({
url: '/kcb/batchDelete',
method: 'post',
data: kbhList
})
}
/**
* 更新课程表
* POST /kcb/update
* body 为 KCB 实体,bh 编号必传
*/
export function updateKcb(data) {
return request({
url: '/kcb/update',
method: 'post',
data
})
}
/**
* 根据编号查询课程表详情
* GET /kcb/get?kbh= (kbh 实为 bh 编号值)
*/
export function getKcb(kbh) {
return request({
url: '/kcb/get',
method: 'get',
params: { kbh }
})
}
/**
* 查询所有课程表数据
* GET /kcb/list
* 返回 List<KCB>,无分页,页面做本地过滤与本地分页
*/
export function listKcb() {
return request({
url: '/kcb/list',
method: 'get'
})
}
@@ -0,0 +1,45 @@
import request from '@/utils/request'
// 课表冲突检查:单维度检查(POST /timetable-conflict/check)
// body: { nd 年度(必填), dimensionCode 维度编码(必填), writeToDb?(落库), jcpch?(批次号) }
// 返回 TimetableConflictCardVO { dimensionCode, title, description, checked, conflictCount }
export function checkConflict(data) {
return request({
url: '/timetable-conflict/check',
method: 'post',
data
})
}
// 课表冲突检查:执行全部维度检查(POST /timetable-conflict/check-all)
// body: { nd 年度(必填), writeToDb? }
// 返回 TimetableConflictSummaryVO { nd, cards[], totalConflictCount, fullyChecked }
export function checkAllConflict(data) {
return request({
url: '/timetable-conflict/check-all',
method: 'post',
data
})
}
// 课表冲突检查:重置检查结果(POST /timetable-conflict/reset)
// body: { nd 年度(必填), clearDb? 是否同时清空落库表 }
// 返回 TimetableConflictSummaryVO(所有卡片未检查状态)
export function resetConflict(data) {
return request({
url: '/timetable-conflict/reset',
method: 'post',
data
})
}
// 课表冲突检查:分页查询冲突明细(GET /timetable-conflict/details)
// params: { nd 年度(必填), dimensionCode? 维度编码, pageNum?, pageSize?, useDb?, jcpch? }
// 返回 PageResult<TimetableConflictDetailVO>
export function getConflictDetails(params) {
return request({
url: '/timetable-conflict/details',
method: 'get',
params
})
}
@@ -0,0 +1,71 @@
import request from '@/utils/request'
// 人才培养方案(专业表 ZYB /training)
// 接口依据:TrainingProgramController(前端 baseURL 为 /api,此处不重复 /api 前缀)
/**
* 新增培养方案(专业)
* POST /training/add
* 请求体(ZYB):zydh/zymc/zydm/pxcc/pxlx 必填;
* 主键 zydh 需手动传入(留空时后端自动生成 UUID,后端同时置 ty=false、qysj=当前时间)
*/
export function addTraining(data) {
return request({
url: '/training/add',
method: 'post',
data
})
}
/**
* 停用(逻辑删除)培养方案
* POST /training/disable?zydh=
* @param zydh 专业代号(必填)
*/
export function disableTraining(zydh) {
return request({
url: '/training/disable',
method: 'post',
params: { zydh }
})
}
/**
* 更新培养方案
* POST /training/update
* 请求体(ZYB):zydh 必传
*/
export function updateTraining(data) {
return request({
url: '/training/update',
method: 'post',
data
})
}
/**
* 根据专业代号查询培养方案详情
* GET /training/get?zydh=
* @param zydh 专业代号
*/
export function getTraining(zydh) {
return request({
url: '/training/get',
method: 'get',
params: { zydh }
})
}
/**
* 分页条件查询培养方案列表
* GET /training/list
* 查询参数:zymc(模糊)、zydm(模糊)、pxlx(精确)、pxcc(精确)、pageNum、pageSize
* 返回:PageResult<ZYB>(records/total)
*/
export function listTraining(params) {
return request({
url: '/training/list',
method: 'get',
params
})
}
+70
View File
@@ -0,0 +1,70 @@
import request from '@/utils/request'
// 课程科目(课标 KB)管理(JYSController /jys/kb)
// 接口依据:JYSController 控制器(前端 baseURL 为 /api,此处不重复 /api 前缀)
// 实体 KB 主键为 kbh(课编号);新增时 kbh 留空由后端生成 UUID;xhbs 字段无需传递(后端 NEVER)
/**
* 新增课程科目
* POST /jys/kb/add
* body 为 KB 实体;kbh 可留空(后端生成 UUID)
*/
export function addSubject(data) {
return request({
url: '/jys/kb/add',
method: 'post',
data
})
}
/**
* 删除课程科目
* POST /jys/kb/delete?kbh=
*/
export function deleteSubject(kbh) {
return request({
url: '/jys/kb/delete',
method: 'post',
params: { kbh }
})
}
/**
* 编辑课程科目
* POST /jys/kb/update
* body 为 KB 实体,kbh 课编号必传
*/
export function updateSubject(data) {
return request({
url: '/jys/kb/update',
method: 'post',
data
})
}
/**
* 查询课程科目详情
* GET /jys/kb/get?kbh=
*/
export function getSubject(kbh) {
return request({
url: '/jys/kb/get',
method: 'get',
params: { kbh }
})
}
/**
* 分页查询课程科目(课标 KB)
* GET /jys/kb/list
* 查询参数:pageNum、pageSize、kmc 课名称(模糊)、jc 简称(模糊)、kmdm 科目代码(模糊)、
* jysdh 教研室代号(等值)、kclx 课程类型、pxlx 培训类型、pxcc 培训层次
* 返回:PageResult<KB>(records/total)
*/
export function listSubject(params) {
return request({
url: '/jys/kb/list',
method: 'get',
params
})
}
+75
View File
@@ -0,0 +1,75 @@
import request from '@/utils/request'
// 教员管理:教员(JYB)+ 教员属性(JYSX)
// 接口依据:/jys/teacher/*(前端 baseURL 为 /api,此处不重复 /api 前缀)
/**
* 分页查询教员列表
* GET /jys/teacher/list
* 查询参数(JYBMapper.xml 支持):jyxm 模糊、jysdh 等值、zc 模糊、jylb 等值
*/
export function listTeacher(params) {
return request({
url: '/jys/teacher/list',
method: 'get',
params
})
}
/** 根据教员编号查询教员详情 GET /jys/teacher/get?jybh= */
export function getTeacher(jybh) {
return request({
url: '/jys/teacher/get',
method: 'get',
params: { jybh }
})
}
/**
* 新增教员 POST /jys/teacher/add
* body 为 AddTeacherDTO:{ teacher: JYB, attributes: JYSX }
* lzzt 前端不传,后端自动置 0
*/
export function addTeacher(data) {
return request({
url: '/jys/teacher/add',
method: 'post',
data
})
}
/** 更新教员基本信息 POST /jys/teacher/update(body 为 JYB,jybh 必传) */
export function updateTeacher(data) {
return request({
url: '/jys/teacher/update',
method: 'post',
data
})
}
/** 教员离职(逻辑删除)POST /jys/teacher/disable?jybh= */
export function disableTeacher(jybh) {
return request({
url: '/jys/teacher/disable',
method: 'post',
params: { jybh }
})
}
/** 查询教员属性 GET /jys/teacher/attribute/get?jybh= */
export function getTeacherAttribute(jybh) {
return request({
url: '/jys/teacher/attribute/get',
method: 'get',
params: { jybh }
})
}
/** 更新教员属性 POST /jys/teacher/attribute/update(body 为 JYSX,jybh 必传) */
export function updateTeacherAttribute(data) {
return request({
url: '/jys/teacher/attribute/update',
method: 'post',
data
})
}
@@ -0,0 +1,83 @@
import request from '@/utils/request'
// 教材管理:教材信息(JCXX 实体)
// 接口依据 api.txt:/teachingMaterial/*(前端 baseURL 为 /api,此处不重复 /api 前缀)
/**
* 教材信息分页查询
* GET /teachingMaterial/list
* 查询参数(TeachingMaterialMapper.xml 支持):bh/mc/isbn/cbs/zz 模糊,jcfl/jclx 等值,ty 等值
*/
export function listTeachingMaterial(params) {
return request({
url: '/teachingMaterial/list',
method: 'get',
params
})
}
/** 根据编号查询 GET /teachingMaterial/getByBh?bh= */
export function getTeachingMaterialByBh(bh) {
return request({
url: '/teachingMaterial/getByBh',
method: 'get',
params: { bh }
})
}
/** 根据ID查询 GET /teachingMaterial/getById?id= */
export function getTeachingMaterialById(id) {
return request({
url: '/teachingMaterial/getById',
method: 'get',
params: { id }
})
}
/** 新增教材信息 POST /teachingMaterial/add */
export function addTeachingMaterial(data) {
return request({
url: '/teachingMaterial/add',
method: 'post',
data
})
}
/** 根据ID修改 POST /teachingMaterial/update(body 需携带 id) */
export function updateTeachingMaterial(data) {
return request({
url: '/teachingMaterial/update',
method: 'post',
data
})
}
/** 删除 POST /teachingMaterial/delete?id= */
export function deleteTeachingMaterial(id) {
return request({
url: '/teachingMaterial/delete',
method: 'post',
params: { id }
})
}
/** 教材导入模板下载 GET /teachingMaterial/template */
export function downloadTeachingMaterialTemplate() {
return request({
url: '/teachingMaterial/template',
method: 'get',
responseType: 'blob'
})
}
/** 教材导入 POST /teachingMaterial/import(form-data,字段名 file) */
export function importTeachingMaterial(file) {
const formData = new FormData()
formData.append('file', file)
return request({
url: '/teachingMaterial/import',
method: 'post',
data: formData,
headers: { 'Content-Type': 'multipart/form-data' }
})
}
+106
View File
@@ -0,0 +1,106 @@
import request from '@/utils/request'
// 教材库存:库存单(JCKCD)+ 库存明细项(JCKCMXX)
// 接口依据 api.txt:/teachingStock/*(前端 baseURL 为 /api,此处不重复 /api 前缀)
// ==================== 教材库存单(JCKCD) ====================
/**
* 分页查询库存单
* GET /teachingStock/list
* 查询参数(TeachingStockMapper.xml 支持):bh/mc 模糊,lb/zb/zt 等值
*/
export function listTeachingStock(params) {
return request({
url: '/teachingStock/list',
method: 'get',
params
})
}
/** 根据ID查询库存单 GET /teachingStock/get?id= */
export function getTeachingStock(id) {
return request({
url: '/teachingStock/get',
method: 'get',
params: { id }
})
}
/** 新增库存单 POST /teachingStock/add(bh 留空由后端自动生成) */
export function addTeachingStock(data) {
return request({
url: '/teachingStock/add',
method: 'post',
data
})
}
/** 修改库存单 POST /teachingStock/update(body 需携带 id) */
export function updateTeachingStock(data) {
return request({
url: '/teachingStock/update',
method: 'post',
data
})
}
/** 删除库存单 POST /teachingStock/delete?id= */
export function deleteTeachingStock(id) {
return request({
url: '/teachingStock/delete',
method: 'post',
params: { id }
})
}
// ==================== 教材库存明细项(JCKCMXX) ====================
/**
* 分页查询库存明细项
* GET /teachingStock/detail/list
* 查询参数(TeachingStockDetailMapper.xml 支持):bh 模糊,jckcdbbh 等值,jcxxbh 模糊,hwbw 等值
*/
export function listTeachingStockDetail(params) {
return request({
url: '/teachingStock/detail/list',
method: 'get',
params
})
}
/** 根据ID查询库存明细项 GET /teachingStock/detail/get?id= */
export function getTeachingStockDetail(id) {
return request({
url: '/teachingStock/detail/get',
method: 'get',
params: { id }
})
}
/** 新增库存明细项 POST /teachingStock/detail/add(bh 留空由后端自动生成) */
export function addTeachingStockDetail(data) {
return request({
url: '/teachingStock/detail/add',
method: 'post',
data
})
}
/** 修改库存明细项 POST /teachingStock/detail/update(body 需携带 id) */
export function updateTeachingStockDetail(data) {
return request({
url: '/teachingStock/detail/update',
method: 'post',
data
})
}
/** 删除库存明细项 POST /teachingStock/detail/delete?id= */
export function deleteTeachingStockDetail(id) {
return request({
url: '/teachingStock/detail/delete',
method: 'post',
params: { id }
})
}
+55 -1
View File
@@ -1,4 +1,4 @@
@import './variables.scss';
@import './variables.scss';
@import './mixin.scss';
@import './transition.scss';
@import './element-ui.scss';
@@ -41,6 +41,60 @@ html {
height: 100%;
}
/* ================= 全局自适应加固 =================
与 src/utils/adaptive.js 的视口等比缩放配合,
兜底避免极端情况下出现页面级横向溢出与内容崩塌。 */
html,
body {
width: 100%;
}
body {
/* 禁止页面级横向滚动,防止因个别固定宽度元素把整页撑破;
表格自身的横向滚动由 el-table 内部滚动容器承担,不受影响 */
overflow-x: hidden;
}
/* 弹性布局子项允许收缩,避免 flex 容器被子项固有宽度撑破 */
#app,
.app-wrapper,
.main-container,
.lower-container {
min-width: 0;
}
/* ===== Element 浮层逆缩放(配合 adaptive.js) =====
挂到 body 的浮层(下拉/气泡/日期/对话框/消息等)基于 getBoundingClientRect 定位,
该值受 html 的 zoom 缩放影响;若浮层自身也继承同一 zoom,坐标会被“重复缩放”而错位。
这里用 adaptive.js 注入的 --adaptive-scale-inv(= 1/当前缩放倍数)把这些浮层还原为 1:1,
使其与触发元素在视觉坐标上正确对齐。缩放=1 时该变量为 1,不产生任何影响。 */
:root {
--adaptive-scale: 1;
}
.el-popper,
.el-select-dropdown,
.el-dropdown-menu,
.el-picker-panel,
.el-cascader__dropdown,
.el-message-box__wrapper,
.el-dialog__wrapper,
.el-message,
.el-notification,
.el-loading-mask,
.v-modal {
zoom: var(--adaptive-scale-inv, 1);
}
/* 通用长文本换行,避免 url/数字/英文长串将布局撑破 */
a,
.el-button,
.el-table .cell,
.el-form-item__content,
.el-form-item__label {
word-break: break-word;
}
*,
*:before,
*:after {
+20 -1
View File
@@ -1,4 +1,4 @@
/**
/**
* 通用css样式布局处理
* Copyright (c) 2019 roomroot
*/
@@ -93,6 +93,11 @@
}
.el-table {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
.el-table__header-wrapper, .el-table__fixed-header-wrapper {
th {
word-break: break-word;
@@ -108,6 +113,20 @@
margin-left: 1px;
}
}
// 表格横向拖拽滚动:拖拽过程中保持默认光标,不显示“抓手”,避免干扰操作手感
.el-table__body-wrapper.is-dragging,
.el-table__fixed-body-wrapper.is-dragging {
cursor: default;
}
}
/* 原生表格同样禁止选中文本,避免横向滑动/滚动时误触发复制前的文本选中 */
table {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
/** 表单布局 **/
+11 -8
View File
@@ -1,4 +1,4 @@
/**
/**
* 通用css样式布局处理
* Copyright (c) 2019 roomroot
*/
@@ -93,13 +93,16 @@
}
.el-table {
.el-table__header-wrapper, .el-table__fixed-header-wrapper {
th {
word-break: break-word;
background-color: #f8f8f9;
color: #515a6e;
height: 40px;
font-size: 13px;
th {
word-break: break-word;
background-color: #f8f8f9;
color: #515a6e;
height: 40px;
font-size: 13px;
text-align: center;
.cell {
text-align: center;
}
}
@@ -92,9 +92,6 @@ export default {
}
},
computed: {
theme() {
return this.$store.state.settings.theme
},
routes() {
return this.$store.getters.defaultRoutes
}
@@ -213,7 +210,7 @@ export default {
activeStyle(index) {
if (index !== this.activeIndex) return {}
return {
'background-color': this.theme,
'background-color': '#00875A',
'color': '#fff'
}
},
@@ -1,170 +0,0 @@
<template>
<el-color-picker
v-model="theme"
:predefine="['#00875A', '#006B47', '#007F4F', '#11a983', '#13c2c2', '#6959CD', '#f5222d', '#212121', ]"
class="theme-picker"
popper-class="theme-picker-dropdown"
/>
</template>
<script>
const ORIGINAL_THEME = '#00875A' // default color
export default {
data() {
return {
chalk: '', // content of theme-chalk css
theme: ''
}
},
computed: {
defaultTheme() {
return this.$store.state.settings.theme
}
},
watch: {
defaultTheme: {
handler: function(val, oldVal) {
this.theme = val
},
immediate: true
},
async theme(val) {
await this.setTheme(val)
}
},
created() {
if (this.defaultTheme !== ORIGINAL_THEME) {
this.setTheme(this.defaultTheme)
}
},
methods: {
async setTheme(val) {
const oldVal = this.chalk ? this.theme : ORIGINAL_THEME
if (typeof val !== 'string') return
const themeCluster = this.getThemeCluster(val.replace('#', ''))
const originalCluster = this.getThemeCluster(oldVal.replace('#', ''))
const getHandler = (variable, id) => {
return () => {
const originalCluster = this.getThemeCluster(ORIGINAL_THEME.replace('#', ''))
const newStyle = this.updateStyle(this[variable], originalCluster, themeCluster)
let styleTag = document.getElementById(id)
if (!styleTag) {
styleTag = document.createElement('style')
styleTag.setAttribute('id', id)
document.head.appendChild(styleTag)
}
styleTag.innerText = newStyle
}
}
if (!this.chalk) {
const url = `/styles/theme-chalk/index.css`
await this.getCSSString(url, 'chalk')
}
const chalkHandler = getHandler('chalk', 'chalk-style')
chalkHandler()
const styles = [].slice.call(document.querySelectorAll('style'))
.filter(style => {
const text = style.innerText
return new RegExp(oldVal, 'i').test(text) && !/Chalk Variables/.test(text)
})
styles.forEach(style => {
const { innerText } = style
if (typeof innerText !== 'string') return
style.innerText = this.updateStyle(innerText, originalCluster, themeCluster)
})
this.$emit('change', val)
},
updateStyle(style, oldCluster, newCluster) {
let newStyle = style
oldCluster.forEach((color, index) => {
newStyle = newStyle.replace(new RegExp(color, 'ig'), newCluster[index])
})
return newStyle
},
getCSSString(url, variable) {
return new Promise(resolve => {
const xhr = new XMLHttpRequest()
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status === 200) {
this[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, '')
resolve()
}
}
xhr.open('GET', url)
xhr.send()
})
},
getThemeCluster(theme) {
const tintColor = (color, tint) => {
let red = parseInt(color.slice(0, 2), 16)
let green = parseInt(color.slice(2, 4), 16)
let blue = parseInt(color.slice(4, 6), 16)
if (tint === 0) { // when primary color is in its rgb space
return [red, green, blue].join(',')
} else {
red += Math.round(tint * (255 - red))
green += Math.round(tint * (255 - green))
blue += Math.round(tint * (255 - blue))
red = red.toString(16)
green = green.toString(16)
blue = blue.toString(16)
return `#${red}${green}${blue}`
}
}
const shadeColor = (color, shade) => {
let red = parseInt(color.slice(0, 2), 16)
let green = parseInt(color.slice(2, 4), 16)
let blue = parseInt(color.slice(4, 6), 16)
red = Math.round((1 - shade) * red)
green = Math.round((1 - shade) * green)
blue = Math.round((1 - shade) * blue)
red = red.toString(16)
green = green.toString(16)
blue = blue.toString(16)
return `#${red}${green}${blue}`
}
const clusters = [theme]
for (let i = 0; i <= 9; i++) {
clusters.push(tintColor(theme, Number((i / 10).toFixed(2))))
}
clusters.push(shadeColor(theme, 0.1))
return clusters
}
}
}
</script>
<style>
.theme-message,
.theme-picker-dropdown {
z-index: 99999 !important;
}
.theme-picker .el-color-picker__trigger {
height: 26px !important;
width: 26px !important;
padding: 2px;
}
.theme-picker-dropdown .el-color-dropdown__link-btn {
display: none;
}
</style>
+4 -1
View File
@@ -1,9 +1,10 @@
import hasRole from './permission/hasRole'
import hasRole from './permission/hasRole'
import hasPermi from './permission/hasPermi'
import dialogDrag from './dialog/drag'
import dialogDragWidth from './dialog/dragWidth'
import dialogDragHeight from './dialog/dragHeight'
import clipboard from './module/clipboard'
import tableDrag from './tableDrag'
const install = function(Vue) {
Vue.directive('hasRole', hasRole)
@@ -12,6 +13,8 @@ const install = function(Vue) {
Vue.directive('dialogDrag', dialogDrag)
Vue.directive('dialogDragWidth', dialogDragWidth)
Vue.directive('dialogDragHeight', dialogDragHeight)
// 全局表格横向拖拽滚动:通过 document 事件委托自动作用于所有 el-table
tableDrag()
}
if (window.Vue) {
+1 -7
View File
@@ -6,17 +6,15 @@
</keep-alive>
</transition>
<iframe-toggle />
<copyright />
</section>
</template>
<script>
import copyright from "./Copyright/index"
import iframeToggle from "./IframeToggle/index"
export default {
name: 'AppMain',
components: { iframeToggle, copyright },
components: { iframeToggle },
computed: {
cachedViews() {
return this.$store.state.tagsView.cachedViews
@@ -64,10 +62,6 @@ export default {
}
}
.app-main:has(.copyright) {
padding-bottom: 36px;
}
.hasTagsView {
.app-main {
/* tags-view 高度 34px,AppMain 在 flex 列中由父容器约束高度 */
@@ -1,35 +0,0 @@
<template>
<footer v-if="visible" class="copyright">
<span>{{ content }}</span>
</footer>
</template>
<script>
export default {
computed: {
visible() {
return this.$store.state.settings.footerVisible
},
content() {
return this.$store.state.settings.footerContent
}
}
}
</script>
<style scoped>
.copyright {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 36px;
padding: 10px 20px;
text-align: right;
background-color: #f8f8f8;
color: #666;
font-size: 14px;
border-top: 1px solid #e7e7e7;
z-index: 999;
}
</style>
+2 -21
View File
@@ -1,9 +1,8 @@
<template>
<div class="navbar" :class="['nav' + navType, { 'header-mode': headerMode }]">
<div class="navbar" :class="[{ 'header-mode': headerMode }]">
<hamburger v-if="!headerMode" id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
<breadcrumb v-if="!headerMode && navType == 1" id="breadcrumb-container" class="breadcrumb-container" />
<top-nav v-if="!headerMode && navType == 2" id="topmenu-container" class="topmenu-container" />
<breadcrumb v-if="!headerMode" id="breadcrumb-container" class="breadcrumb-container" />
<div class="right-menu">
<template v-if="device!=='mobile'">
<el-tooltip content="菜单搜索" effect="dark" placement="bottom">
@@ -34,9 +33,6 @@
<router-link to="/user/profile">
<el-dropdown-item icon="el-icon-user">个人中心</el-dropdown-item>
</router-link>
<el-dropdown-item v-if="setting" icon="el-icon-s-tools" @click.native="setLayout">
<span>布局设置</span>
</el-dropdown-item>
<el-dropdown-item icon="el-icon-lock" @click.native="lockScreen">
<span>锁定屏幕</span>
</el-dropdown-item>
@@ -52,7 +48,6 @@
<script>
import { mapGetters } from 'vuex'
import Breadcrumb from '@/components/Breadcrumb'
import TopNav from './TopNav'
import Hamburger from '@/components/Hamburger'
import Screenfull from '@/components/Screenfull'
import Search from '@/components/HeaderSearch'
@@ -61,7 +56,6 @@ import HeaderNotice from './HeaderNotice'
export default {
components: {
Breadcrumb,
TopNav,
Hamburger,
Screenfull,
Search,
@@ -85,16 +79,6 @@ export default {
'nickName',
'roles'
]),
setting: {
get() {
return this.$store.state.settings.showSettings
}
},
navType: {
get() {
return this.$store.state.settings.navType
}
},
// 用户角色标签:根据 roles 数组智能映射为友好文本
userRoleLabel() {
const roleMap = {
@@ -112,9 +96,6 @@ export default {
toggleSideBar() {
this.$store.dispatch('app/toggleSideBar')
},
setLayout(event) {
this.$emit('setLayout')
},
handleCommand(command) {
// 预留:下拉菜单 command 事件入口
},
-219
View File
@@ -1,219 +0,0 @@
<template>
<el-drawer
:visible.sync="visible"
direction="rtl"
size="280px"
:title="'布局设置'"
class="drawer-setting"
:with-header="false"
:modal-append-to-body="false"
>
<div class="setting-drawer-index">
<div class="setting-drawer-index__title">主题色</div>
<div class="setting-drawer-index__main-color theme-color">
<ul class="ul-color">
<li
v-for="item in colorList"
:key="item.color"
class="li-theme"
:class="{ active: item.color === theme }"
:style="{ background: item.color }"
@click="changeTheme(item.color)"
>
<i v-show="item.color === theme" class="icon-check el-icon-check" />
</li>
</ul>
</div>
<div class="setting-drawer-index__title">界面功能</div>
<div class="setting-item">
<span class="setting-item__label">侧边栏颜色</span>
<el-dropdown trigger="click" @command="handleSideTheme">
<span class="setting-item__value">
{{ sideThemeLabel }}
<i class="el-icon-arrow-down" />
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="theme-dark">深色主题</el-dropdown-item>
<el-dropdown-item command="theme-light">浅色主题</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
<div class="setting-item">
<span class="setting-item__label">导航栏模式</span>
<el-dropdown trigger="click" @command="handleNavType">
<span class="setting-item__value">
{{ navTypeLabel }}
<i class="el-icon-arrow-down" />
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="1">侧边栏模式</el-dropdown-item>
<el-dropdown-item command="2">顶部导航模式</el-dropdown-item>
<el-dropdown-item command="3">侧栏+顶栏</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
<div class="setting-item">
<span class="setting-item__label"> TagsView 标签栏</span>
<el-switch
:value="tagsView"
@change="changeSetting({ key: 'tagsView', value: !tagsView })"
/>
</div>
<div class="setting-item">
<span class="setting-item__label"> 侧边栏 Logo</span>
<el-switch
:value="sidebarLogo"
@change="changeSetting({ key: 'sidebarLogo', value: !sidebarLogo })"
/>
</div>
<div class="setting-item">
<span class="setting-item__label"> 固定 Header</span>
<el-switch
:value="fixedHeader"
@change="changeSetting({ key: 'fixedHeader', value: !fixedHeader })"
/>
</div>
</div>
</el-drawer>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'Settings',
data() {
return {
visible: false,
/**
* 主题色列表
*/
colorList: [
{ color: '#00875A', title: '教育绿' },
{ color: '#304156', title: '深蓝' },
{ color: '#409EFF', title: '亮蓝' },
{ color: '#626AEF', title: '亮紫' },
{ color: '#EB2F96', title: '粉红' },
{ color: '#F8721F', title: '橙色' },
{ color: '#CD201F', title: '红色' }
]
}
},
computed: {
...mapState({
theme: state => state.settings.theme,
sideTheme: state => state.settings.sideTheme,
navType: state => state.settings.navType,
tagsView: state => state.settings.tagsView,
sidebarLogo: state => state.settings.sidebarLogo,
fixedHeader: state => state.settings.fixedHeader
}),
sideThemeLabel() {
return this.sideTheme === 'theme-dark' ? '深色主题' : '浅色主题'
},
navTypeLabel() {
const map = { 1: '侧边栏模式', 2: '顶部导航模式', 3: '侧栏+顶栏' }
return map[this.navType] || '侧边栏模式'
}
},
methods: {
openSetting() {
this.visible = true
},
closeSetting() {
this.visible = false
},
// 修改主题色
changeTheme(color) {
this.$store.dispatch('settings/changeSetting', { key: 'theme', value: color })
},
handleSideTheme(command) {
this.changeSetting({ key: 'sideTheme', value: command })
},
handleNavType(command) {
this.changeSetting({ key: 'navType', value: Number(command) })
},
// 修改布局设置,并持久化到本地存储
changeSetting(data) {
this.$store.dispatch('settings/changeSetting', data)
const setting = JSON.parse(localStorage.getItem('layout-setting') || '{}')
setting[data.key] = data.value
localStorage.setItem('layout-setting', JSON.stringify(setting))
}
}
}
</script>
<style lang="scss" scoped>
.setting-drawer-index {
padding: 0 20px;
box-sizing: border-box;
&__title {
margin-top: 26px;
margin-bottom: 12px;
font-weight: 700;
color: #333;
}
&__main-color {
border-radius: 4px;
}
.theme-color {
.ul-color {
margin: 0;
padding: 0;
.li-theme {
list-style: none;
display: inline-block;
width: 30px;
height: 30px;
margin-right: 6px;
border-radius: 50%;
cursor: pointer;
text-align: center;
vertical-align: middle;
&.active {
box-shadow: 0 0 0 2px #fff, 0 0 0 4px currentColor;
}
.icon-check {
color: #fff;
font-size: 14px;
line-height: 30px;
}
}
}
}
.setting-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 0;
font-size: 14px;
color: #606266;
&__value {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 0 12px;
height: 30px;
line-height: 30px;
border-radius: 4px;
border: 1px solid #dcdfe6;
cursor: pointer;
font-size: 13px;
}
}
}
</style>
@@ -1,430 +0,0 @@
<template>
<el-drawer size="280px" :visible="showSettings" :with-header="false" :append-to-body="true" :before-close="closeSetting" :lock-scroll="false">
<div class="drawer-container">
<div>
<div class="setting-drawer-content">
<div class="setting-drawer-title">
<h3 class="drawer-title">菜单导航设置</h3>
</div>
<div class="nav-wrap">
<el-tooltip content="左侧菜单" placement="bottom">
<div class="item left" @click="handleNavType(1)" :style="{'--theme': theme}" :class="{ activeItem: navType == 1 }">
<b></b><b></b>
</div>
</el-tooltip>
<el-tooltip content="混合菜单" placement="bottom">
<div class="item mix" @click="handleNavType(2)" :style="{'--theme': theme}" :class="{ activeItem: navType == 2 }">
<b></b><b></b>
</div>
</el-tooltip>
<el-tooltip content="顶部菜单" placement="bottom">
<div class="item top" @click="handleNavType(3)" :style="{'--theme': theme}" :class="{ activeItem: navType == 3 }">
<b></b><b></b>
</div>
</el-tooltip>
</div>
<div class="setting-drawer-title">
<h3 class="drawer-title">主题风格设置</h3>
</div>
<div class="setting-drawer-block-checbox">
<div class="setting-drawer-block-checbox-item" @click="handleTheme('theme-dark')">
<img src="@/assets/images/dark.svg" alt="dark">
<div v-if="sideTheme === 'theme-dark'" class="setting-drawer-block-checbox-selectIcon" style="display: block;">
<i aria-label="图标: check" class="anticon anticon-check">
<svg viewBox="64 64 896 896" data-icon="check" width="1em" height="1em" :fill="theme" aria-hidden="true" focusable="false" class="">
<path d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 0 0-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"/>
</svg>
</i>
</div>
</div>
<div class="setting-drawer-block-checbox-item" @click="handleTheme('theme-light')">
<img src="@/assets/images/light.svg" alt="light">
<div v-if="sideTheme === 'theme-light'" class="setting-drawer-block-checbox-selectIcon" style="display: block;">
<i aria-label="图标: check" class="anticon anticon-check">
<svg viewBox="64 64 896 896" data-icon="check" width="1em" height="1em" :fill="theme" aria-hidden="true" focusable="false" class="">
<path d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 0 0-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"/>
</svg>
</i>
</div>
</div>
</div>
<div class="drawer-item">
<span>主题颜色</span>
<theme-picker style="float: right;height: 26px;margin: -3px 8px 0 0;" @change="themeChange" />
</div>
</div>
<el-divider/>
<h3 class="drawer-title">系统布局配置</h3>
<div class="drawer-item">
<span>开启页签</span>
<el-switch v-model="tagsView" class="drawer-switch" />
</div>
<div class="drawer-item">
<span>持久化标签页</span>
<el-switch v-model="tagsViewPersist" :disabled="!tagsView" class="drawer-switch" />
</div>
<div class="drawer-item">
<span>显示页签图标</span>
<el-switch v-model="tagsIcon" :disabled="!tagsView" class="drawer-switch" />
</div>
<div class="drawer-item">
<span>标签页样式</span>
<el-radio-group v-model="tagsViewStyle" :disabled="!tagsView" size="mini" class="drawer-switch">
<el-radio-button label="card">卡片</el-radio-button>
<el-radio-button label="chrome">谷歌</el-radio-button>
</el-radio-group>
</div>
<div class="drawer-item">
<span>固定 Header</span>
<el-switch v-model="fixedHeader" class="drawer-switch" />
</div>
<div class="drawer-item">
<span>显示 Logo</span>
<el-switch v-model="sidebarLogo" class="drawer-switch" />
</div>
<div class="drawer-item">
<span>动态标题</span>
<el-switch v-model="dynamicTitle" class="drawer-switch" />
</div>
<div class="drawer-item">
<span>底部版权</span>
<el-switch v-model="footerVisible" class="drawer-switch" />
</div>
<el-divider/>
<el-button size="small" type="primary" plain icon="el-icon-document-add" @click="saveSetting">保存配置</el-button>
<el-button size="small" plain icon="el-icon-refresh" @click="resetSetting">重置配置</el-button>
</div>
</div>
</el-drawer>
</template>
<script>
import ThemePicker from '@/components/ThemePicker'
export default {
components: { ThemePicker },
expose: ['openSetting'],
data() {
return {
theme: this.$store.state.settings.theme,
sideTheme: this.$store.state.settings.sideTheme,
navType: this.$store.state.settings.navType,
showSettings: false
}
},
computed: {
fixedHeader: {
get() {
return this.$store.state.settings.fixedHeader
},
set(val) {
this.$store.dispatch('settings/changeSetting', {
key: 'fixedHeader',
value: val
})
}
},
tagsViewPersist: {
get() {
return this.$store.state.settings.tagsViewPersist
},
set(val) {
this.$store.dispatch('settings/changeSetting', {
key: 'tagsViewPersist',
value: val
})
}
},
tagsView: {
get() {
return this.$store.state.settings.tagsView
},
set(val) {
this.$store.dispatch('settings/changeSetting', {
key: 'tagsView',
value: val
})
}
},
tagsIcon: {
get() {
return this.$store.state.settings.tagsIcon
},
set(val) {
this.$store.dispatch('settings/changeSetting', {
key: 'tagsIcon',
value: val
})
}
},
tagsViewStyle: {
get() {
return this.$store.state.settings.tagsViewStyle
},
set(val) {
this.$store.dispatch('settings/changeSetting', {
key: 'tagsViewStyle',
value: val
})
}
},
sidebarLogo: {
get() {
return this.$store.state.settings.sidebarLogo
},
set(val) {
this.$store.dispatch('settings/changeSetting', {
key: 'sidebarLogo',
value: val
})
}
},
dynamicTitle: {
get() {
return this.$store.state.settings.dynamicTitle
},
set(val) {
this.$store.dispatch('settings/changeSetting', {
key: 'dynamicTitle',
value: val
})
this.$store.dispatch('settings/setTitle', this.$store.state.settings.title)
}
},
footerVisible: {
get() {
return this.$store.state.settings.footerVisible
},
set(val) {
this.$store.dispatch('settings/changeSetting', {
key: 'footerVisible',
value: val
})
}
}
},
watch: {
navType: {
handler(val) {
if (val == 1) {
this.$store.dispatch("app/toggleSideBarHide", false)
}
if (val == 2) {
}
if (val == 3) {
this.$store.dispatch("app/toggleSideBarHide", true)
}
if ([1, 3].includes(val)) {
this.$store.commit("SET_SIDEBAR_ROUTERS",this.$store.state.permission.defaultRoutes)
}
},
immediate: true,
deep: true
}
},
methods: {
themeChange(val) {
this.$store.dispatch('settings/changeSetting', {
key: 'theme',
value: val
})
this.theme = val
},
handleTheme(val) {
this.$store.dispatch('settings/changeSetting', {
key: 'sideTheme',
value: val
})
this.sideTheme = val
},
handleNavType(val) {
this.$store.dispatch('settings/changeSetting', {
key: 'navType',
value: val
})
this.navType = val
},
openSetting() {
this.showSettings = true
},
closeSetting(){
this.showSettings = false
},
saveSetting() {
this.$modal.loading("正在保存到本地,请稍候...")
if (!this.tagsViewPersist) {
this.$cache.local.remove('tags-view-visited')
}
this.$cache.local.set(
"layout-setting",
`{
"navType":${this.navType},
"tagsView":${this.tagsView},
"tagsIcon":${this.tagsIcon},
"tagsViewStyle":"${this.tagsViewStyle}",
"tagsViewPersist":${this.tagsViewPersist},
"fixedHeader":${this.fixedHeader},
"sidebarLogo":${this.sidebarLogo},
"dynamicTitle":${this.dynamicTitle},
"footerVisible":${this.footerVisible},
"sideTheme":"${this.sideTheme}",
"theme":"${this.theme}"
}`
)
setTimeout(this.$modal.closeLoading(), 1000)
},
resetSetting() {
this.$modal.loading("正在清除设置缓存并刷新,请稍候...")
this.$cache.local.remove('tags-view-visited')
this.$cache.local.remove("layout-setting")
setTimeout("window.location.reload()", 1000)
}
}
}
</script>
<style lang="scss" scoped>
.setting-drawer-content {
.setting-drawer-title {
margin-bottom: 12px;
color: rgba(0, 0, 0, .85);
font-size: 14px;
line-height: 22px;
font-weight: bold;
}
.setting-drawer-block-checbox {
display: flex;
justify-content: flex-start;
align-items: center;
margin-top: 10px;
margin-bottom: 20px;
.setting-drawer-block-checbox-item {
position: relative;
margin-right: 16px;
border-radius: 2px;
cursor: pointer;
img {
width: 48px;
height: 48px;
}
.setting-drawer-block-checbox-selectIcon {
position: absolute;
top: 0;
right: 0;
width: 100%;
height: 100%;
padding-top: 15px;
padding-left: 24px;
color: #00875A;
font-weight: 700;
font-size: 14px;
}
}
}
}
.drawer-container {
padding: 20px;
font-size: 14px;
line-height: 1.5;
word-wrap: break-word;
.drawer-title {
margin-bottom: 12px;
color: rgba(0, 0, 0, .85);
font-size: 14px;
line-height: 22px;
}
.drawer-item {
color: rgba(0, 0, 0, .65);
font-size: 14px;
padding: 12px 0;
}
.drawer-switch {
float: right
}
}
// 导航模式
.nav-wrap {
display: flex;
justify-content: flex-start;
align-items: center;
margin-top: 10px;
margin-bottom: 20px;
.activeItem {
border: 2px solid #{'var(--theme)'} !important;
}
.item {
position: relative;
margin-right: 16px;
cursor: pointer;
width: 56px;
height: 48px;
border-radius: 4px;
background: #f0f2f5;
border: 2px solid transparent;
}
.left {
b:first-child {
display: block;
height: 30%;
background: #fff;
}
b:last-child {
width: 30%;
background: #1b2a47;
position: absolute;
height: 100%;
top: 0;
border-radius: 4px 0 0 4px;
}
}
.mix {
b:first-child {
border-radius: 4px 4px 0 0;
display: block;
height: 30%;
background: #1b2a47;
}
b:last-child {
width: 30%;
background: #1b2a47;
position: absolute;
height: 70%;
border-radius: 0 0 0 4px;
}
}
.top {
b:first-child {
display: block;
height: 30%;
background: #1b2a47;
border-radius: 4px 4px 0 0;
}
}
}
</style>
@@ -1,13 +1,13 @@
<template>
<div :class="['sidebar-theme-wrapper', settings.sideTheme]" :style="{ backgroundColor: settings.sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }">
<el-scrollbar :class="settings.sideTheme" wrap-class="scrollbar-wrapper">
<template>
<div :class="['sidebar-theme-wrapper', 'theme-dark']" :style="{ backgroundColor: variables.menuBackground }">
<el-scrollbar class="theme-dark" wrap-class="scrollbar-wrapper">
<el-menu
:default-active="activeMenu"
:collapse="isCollapse"
:background-color="settings.sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground"
:text-color="settings.sideTheme === 'theme-dark' ? variables.menuColor : variables.menuLightColor"
:background-color="variables.menuBackground"
:text-color="variables.menuColor"
:unique-opened="true"
:active-text-color="settings.sideTheme === 'theme-dark' ? '#ffffff' : settings.theme"
:active-text-color="'#ffffff'"
:collapse-transition="false"
mode="vertical"
>
@@ -1,5 +1,5 @@
<template>
<div id="tags-view-container" class="tags-view-container" :class="{ 'tags-view-container--chrome': tagsViewStyle === 'chrome' }" :style="chromeVars">
<template>
<div id="tags-view-container" class="tags-view-container">
<!-- 左切换箭头 -->
<span class="tags-nav-btn tags-nav-btn--left" :class="{ disabled: !canScrollLeft }" @click="scrollLeft">
<i class="el-icon-arrow-left" />
@@ -11,7 +11,7 @@
v-for="tag in visitedViews"
ref="tag"
:key="tag.path"
:class="{ 'active': isActive(tag), 'has-icon': tagsIcon }"
:class="{ 'active': isActive(tag) }"
:to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
tag="span"
class="tags-view-item"
@@ -19,7 +19,6 @@
@click.middle.native="!isAffix(tag) ? closeSelectedTag(tag) : ''"
@contextmenu.prevent.native="openMenu(tag, $event)"
>
<svg-icon v-if="tagsIcon && tag.meta && tag.meta.icon && tag.meta.icon !== '#'" :icon-class="tag.meta.icon" style="margin-right: 3px;" />
{{ tag.title }}
<span v-if="!isAffix(tag)" class="el-icon-close" @click.prevent.stop="closeSelectedTag(tag)" />
</router-link>
@@ -91,26 +90,8 @@ export default {
routes() {
return this.$store.state.permission.routes
},
theme() {
return this.$store.state.settings.theme
},
tagsIcon() {
return this.$store.state.settings.tagsIcon
},
tagsViewStyle() {
return this.$store.state.settings.tagsViewStyle
},
selectedDropdownTag() {
return this.visitedViews.find(v => this.isActive(v)) || {}
},
chromeVars() {
if (this.tagsViewStyle !== 'chrome') return {}
const primary = this.theme || '#00875A'
return {
'--chrome-tab-active-bg': this.mixHexWithWhite(primary, 0.15),
'--chrome-tab-text-active': primary,
'--chrome-wing-r': '10px'
}
}
},
watch: {
@@ -148,24 +129,14 @@ export default {
this.toggleFullscreen()
}
},
mixHexWithWhite(hex, ratio) {
const clean = hex.replace('#', '')
const r = parseInt(clean.substring(0, 2), 16)
const g = parseInt(clean.substring(2, 4), 16)
const b = parseInt(clean.substring(4, 6), 16)
const mr = Math.round(r * ratio + 255 * (1 - ratio))
const mg = Math.round(g * ratio + 255 * (1 - ratio))
const mb = Math.round(b * ratio + 255 * (1 - ratio))
return `rgb(${mr}, ${mg}, ${mb})`
},
isActive(route) {
return route.path === this.$route.path
},
tagActiveStyle(tag) {
if (!this.isActive(tag) || this.tagsViewStyle !== 'card') return {}
if (!this.isActive(tag)) return {}
return {
"background-color": this.theme,
"border-color": this.theme
"background-color": "#00875A",
"border-color": "#00875A"
}
},
isAffix(tag) {
@@ -209,9 +180,6 @@ export default {
return tags
},
initTags() {
if (this.$store.state.settings.tagsViewPersist) {
this.$store.dispatch('tagsView/loadPersistedViews')
}
const affixTags = this.affixTags = this.filterAffixTags(this.routes)
for (const tag of affixTags) {
if (tag.name) {
@@ -1,193 +0,0 @@
<template>
<el-menu
:default-active="activeMenu"
mode="horizontal"
@select="handleSelect"
>
<template v-for="(item, index) in topMenus">
<el-menu-item :style="{'--theme': theme}" :index="item.path" :key="index" v-if="index < visibleNumber">
<svg-icon
v-if="item.meta && item.meta.icon && item.meta.icon !== '#'"
:icon-class="item.meta.icon"/>
{{ item.meta.title }}
</el-menu-item>
</template>
<!-- 顶部菜单超出数量折叠 -->
<el-submenu :style="{'--theme': theme}" index="more" :key="visibleNumber" v-if="topMenus.length > visibleNumber">
<template slot="title">更多菜单</template>
<template v-for="(item, index) in topMenus">
<el-menu-item
:index="item.path"
:key="index"
v-if="index >= visibleNumber">
<svg-icon
v-if="item.meta && item.meta.icon && item.meta.icon !== '#'"
:icon-class="item.meta.icon"/>
{{ item.meta.title }}
</el-menu-item>
</template>
</el-submenu>
</el-menu>
</template>
<script>
import { constantRoutes } from "@/router"
import { isHttp } from "@/utils/validate"
// 隐藏侧边栏路由
const hideList = ['/index', '/user/profile']
export default {
data() {
return {
// 顶部栏初始数
visibleNumber: 5,
// 当前激活菜单的 index
currentIndex: undefined
}
},
computed: {
theme() {
return this.$store.state.settings.theme
},
// 顶部显示菜单
topMenus() {
let topMenus = []
this.routers.map((menu) => {
if (menu.hidden !== true) {
// 兼容顶部栏一级菜单内部跳转
if (menu.path === '/' && menu.children) {
topMenus.push(menu.children[0])
} else {
topMenus.push(menu)
}
}
})
return topMenus
},
// 所有的路由信息
routers() {
return this.$store.state.permission.topbarRouters
},
// 设置子路由
childrenMenus() {
var childrenMenus = []
this.routers.map((router) => {
for (var item in router.children) {
if (router.children[item].parentPath === undefined) {
if (router.path === "/") {
router.children[item].path = "/" + router.children[item].path
} else {
if (!isHttp(router.children[item].path)) {
router.children[item].path = router.path + "/" + router.children[item].path
}
}
router.children[item].parentPath = router.path
}
childrenMenus.push(router.children[item])
}
})
return constantRoutes.concat(childrenMenus)
},
// 默认激活的菜单
activeMenu() {
const path = this.$route.path
let activePath = path
if (path !== undefined && path.lastIndexOf("/") > 0 && hideList.indexOf(path) === -1) {
const tmpPath = path.substring(1, path.length)
if (!this.$route.meta.link) {
activePath = "/" + tmpPath.substring(0, tmpPath.indexOf("/"))
this.$store.dispatch('app/toggleSideBarHide', false)
}
} else if (!this.$route.children) {
activePath = path
this.$store.dispatch('app/toggleSideBarHide', true)
}
this.activeRoutes(activePath)
return activePath
},
},
beforeMount() {
window.addEventListener('resize', this.setVisibleNumber)
},
beforeDestroy() {
window.removeEventListener('resize', this.setVisibleNumber)
},
mounted() {
this.setVisibleNumber()
},
methods: {
// 根据宽度计算设置显示栏数
setVisibleNumber() {
const width = document.body.getBoundingClientRect().width / 3
this.visibleNumber = parseInt(width / 85)
},
// 菜单选择事件
handleSelect(key, keyPath) {
this.currentIndex = key
const route = this.routers.find(item => item.path === key)
if (isHttp(key)) {
// http(s):// 路径新窗口打开
window.open(key, "_blank")
} else if (!route || !route.children) {
// 没有子路由路径内部打开
const routeMenu = this.childrenMenus.find(item => item.path === key)
if (routeMenu && routeMenu.query) {
let query = JSON.parse(routeMenu.query)
this.$router.push({ path: key, query: query })
} else {
this.$router.push({ path: key })
}
this.$store.dispatch('app/toggleSideBarHide', true)
} else {
// 显示左侧联动菜单
this.activeRoutes(key)
this.$store.dispatch('app/toggleSideBarHide', false)
}
},
// 当前激活的路由
activeRoutes(key) {
var routes = []
if (this.childrenMenus && this.childrenMenus.length > 0) {
this.childrenMenus.map((item) => {
if (key == item.parentPath || (key == "index" && "" == item.path)) {
routes.push(item)
}
})
}
if (routes.length > 0) {
this.$store.commit("SET_SIDEBAR_ROUTERS", routes)
} else {
this.$store.dispatch('app/toggleSideBarHide', true)
}
}
}
}
</script>
<style lang="scss">
.topmenu-container.el-menu--horizontal > .el-menu-item {
float: left;
height: 50px !important;
line-height: 50px !important;
color: #303133 !important;
padding: 0 5px !important;
margin: 0 10px !important;
}
.topmenu-container.el-menu--horizontal > .el-menu-item.is-active, .el-menu--horizontal > .el-submenu.is-active .el-submenu__title {
border-bottom: 2px solid #{'var(--theme)'} !important;
color: #303133;
}
/* submenu item */
.topmenu-container.el-menu--horizontal > .el-submenu .el-submenu__title {
float: left;
height: 50px !important;
line-height: 50px !important;
color: #303133 !important;
padding: 0 5px !important;
margin: 0 10px !important;
}
</style>
+1 -2
View File
@@ -1,5 +1,4 @@
export { default as AppMain } from './AppMain'
export { default as AppMain } from './AppMain'
export { default as Navbar } from './Navbar'
export { default as Settings } from './Settings'
export { default as Sidebar } from './Sidebar/index.vue'
export { default as TagsView } from './TagsView/index.vue'
+25 -15
View File
@@ -1,5 +1,5 @@
<template>
<div :class="classObj" class="app-wrapper" :style="{'--current-color': theme, '--current-color-light': theme + '1a', '--current-color-dark-bg': theme + '33'}">
<template>
<div :class="classObj" class="app-wrapper" :style="{'--current-color': '#00875A', '--current-color-light': '#00875A1a', '--current-color-dark-bg': '#00875A33'}">
<div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside"/>
<!-- 顶部全宽 Header -->
@@ -12,26 +12,25 @@
class="mobile-hamburger"
@toggleClick="toggleSideBar"
/>
<span class="sys-title">教学管理信息系统</span>
<span class="sys-title" title="关闭所有已打开的页签" @click="handleCloseAllTabs">教学管理信息系统</span>
</div>
<semester-bar class="top-header__center" />
<navbar class="top-header__right" @setLayout="setLayout" :header-mode="true" />
<navbar class="top-header__right" :header-mode="true" />
</div>
<!-- 下方:侧边栏 + 主内容 -->
<div class="lower-container" :class="{sidebarHide:sidebar.hide}">
<sidebar v-if="!sidebar.hide" class="sidebar-container"/>
<div :class="{hasTagsView:needTagsView,sidebarHide:sidebar.hide}" class="main-container">
<tags-view v-if="needTagsView"/>
<div :class="{hasTagsView:true,sidebarHide:sidebar.hide}" class="main-container">
<tags-view/>
<app-main/>
<settings ref="settingRef"/>
</div>
</div>
</div>
</template>
<script>
import { AppMain, Navbar, Settings, Sidebar, TagsView } from './components'
import { AppMain, Navbar, Sidebar, TagsView } from './components'
import SemesterBar from './components/SemesterBar'
import Hamburger from '@/components/Hamburger'
import ResizeMixin from './mixin/ResizeHandler'
@@ -43,7 +42,6 @@ export default {
components: {
AppMain,
Navbar,
Settings,
Sidebar,
TagsView,
SemesterBar,
@@ -52,11 +50,8 @@ export default {
mixins: [ResizeMixin],
computed: {
...mapState({
theme: state => state.settings.theme,
sideTheme: state => state.settings.sideTheme,
sidebar: state => state.app.sidebar,
device: state => state.app.device,
needTagsView: state => state.settings.tagsView
device: state => state.app.device
}),
classObj() {
return {
@@ -75,8 +70,15 @@ export default {
toggleSideBar() {
this.$store.dispatch('app/toggleSideBar')
},
setLayout() {
this.$refs.settingRef.openSetting()
handleCloseAllTabs() {
this.$store.dispatch('tagsView/delAllViews').then(({ visitedViews }) => {
const last = visitedViews[visitedViews.length - 1]
if (last) {
this.$router.push(last.fullPath).catch(() => {})
} else {
this.$router.push('/').catch(() => {})
}
})
}
}
}
@@ -137,6 +139,14 @@ export default {
white-space: nowrap;
letter-spacing: 1.5px;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.12);
cursor: pointer;
border-radius: 6px;
padding: 4px 10px;
transition: background 0.25s ease;
&:hover {
background: rgba(255, 255, 255, 0.15);
}
}
}
+5 -1
View File
@@ -1,4 +1,4 @@
import Vue from 'vue'
import Vue from 'vue'
import Cookies from 'js-cookie'
@@ -13,6 +13,10 @@ import router from './router'
import directive from './directive' // directive
import plugins from './plugins' // plugins
import { download } from '@/utils/request'
import { initAdaptive } from '@/utils/adaptive' // 全局视口等比缩放自适应
// 初始化全局自适应,保障不同大小屏幕下页面布局不崩塌
initAdaptive()
import './assets/icons' // icon
import './permission' // permission control
+1 -2
View File
@@ -1,4 +1,4 @@
import router from './router'
import router from './router'
import store from './store'
import { Message } from 'element-ui'
import NProgress from 'nprogress'
@@ -18,7 +18,6 @@ const isWhiteList = (path) => {
router.beforeEach((to, from, next) => {
NProgress.start()
if (getToken()) {
to.meta.title && store.dispatch('settings/setTitle', to.meta.title)
const isLock = store.getters.isLock
/* has token*/
if (to.path === '/login') {
-66
View File
@@ -1,66 +0,0 @@
module.exports = {
/**
* 网页标题
*/
title: process.env.VUE_APP_TITLE,
/**
* 侧边栏主题 深色主题theme-dark,浅色主题theme-light
*/
sideTheme: 'theme-dark',
/**
* 系统布局配置
*/
showSettings: true,
/**
* 菜单导航模式 1、纯左侧 2、混合(左侧+顶部) 3、纯顶部
*/
navType: 1,
/**
* 是否显示 tagsView
*/
tagsView: true,
/**
* 持久化标签页
*/
tagsViewPersist: false,
/**
* 显示页签图标
*/
tagsIcon: false,
/**
* 标签页样式:card 卡片(默认)、chrome 谷歌浏览器风格
*/
tagsViewStyle: 'card',
/**
* 是否固定头部
*/
fixedHeader: true,
/**
* 是否显示logo
*/
sidebarLogo: true,
/**
* 是否显示动态标题
*/
dynamicTitle: false,
/**
* 是否显示底部版权
*/
footerVisible: false,
/**
* 底部版权文本内容
*/
footerContent: 'Copyright © 2018-2026 roomroot. All Rights Reserved.'
}
+2 -4
View File
@@ -1,4 +1,4 @@
import Vue from 'vue'
import Vue from 'vue'
import Vuex from 'vuex'
import app from './modules/app'
import lock from './modules/lock'
@@ -6,7 +6,6 @@ import dict from './modules/dict'
import user from './modules/user'
import tagsView from './modules/tagsView'
import permission from './modules/permission'
import settings from './modules/settings'
import getters from './getters'
Vue.use(Vuex)
@@ -18,8 +17,7 @@ const store = new Vuex.Store({
dict,
user,
tagsView,
permission,
settings
permission
},
getters
})
-52
View File
@@ -1,52 +0,0 @@
import defaultSettings from '@/settings'
import { useDynamicTitle } from '@/utils/dynamicTitle'
const { sideTheme, showSettings, navType, tagsView, tagsViewPersist, tagsIcon, tagsViewStyle, fixedHeader, sidebarLogo, dynamicTitle, footerVisible, footerContent } = defaultSettings
const storageSetting = JSON.parse(localStorage.getItem('layout-setting')) || ''
const state = {
title: '',
theme: storageSetting.theme || '#00875A',
sideTheme: storageSetting.sideTheme || sideTheme,
showSettings: showSettings,
navType: storageSetting.navType === undefined ? navType : storageSetting.navType,
tagsView: storageSetting.tagsView === undefined ? tagsView : storageSetting.tagsView,
tagsViewPersist: storageSetting.tagsViewPersist === undefined ? tagsViewPersist : storageSetting.tagsViewPersist,
tagsIcon: storageSetting.tagsIcon === undefined ? tagsIcon : storageSetting.tagsIcon,
tagsViewStyle: storageSetting.tagsViewStyle === undefined ? tagsViewStyle : storageSetting.tagsViewStyle,
fixedHeader: storageSetting.fixedHeader === undefined ? fixedHeader : storageSetting.fixedHeader,
sidebarLogo: storageSetting.sidebarLogo === undefined ? sidebarLogo : storageSetting.sidebarLogo,
dynamicTitle: storageSetting.dynamicTitle === undefined ? dynamicTitle : storageSetting.dynamicTitle,
footerVisible: storageSetting.footerVisible === undefined ? footerVisible : storageSetting.footerVisible,
footerContent: footerContent
}
const mutations = {
CHANGE_SETTING: (state, { key, value }) => {
if (state.hasOwnProperty(key)) {
state[key] = value
}
},
SET_TITLE: (state, title) => {
state.title = title
}
}
const actions = {
// 修改布局设置
changeSetting({ commit }, data) {
commit('CHANGE_SETTING', data)
},
// 设置网页标题
setTitle({ commit }, title) {
commit('SET_TITLE', title)
useDynamicTitle()
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
+2 -2
View File
@@ -1,10 +1,10 @@
import store from '@/store'
import cache from '@/plugins/cache'
const PERSIST_KEY = 'tags-view-visited'
// 布局已固定,不启用标签页持久化
function isPersistEnabled() {
return store.state.settings.tagsViewPersist
return false
}
function saveVisitedViews(views) {
+161
View File
@@ -0,0 +1,161 @@
/**
* 全局视口等比缩放 —— 让整个系统在不同大小屏幕(桌面 1280~4K)下布局不崩塌。
*
* 原理:
* 以 DESIGN_WIDTH(1920)为设计基准宽度。将视口宽度与设计宽的比值作为缩放倍数,
* 作用到文档根节点 <html> 的 zoom 上:
* - 视口 < 设计宽(如 1280):zoom < 1,整体等比缩小,避免窄屏布局被撑破;
* - 视口 = 设计宽(1920):zoom = 1,原生 1:1 显示;
* - 视口 > 设计宽(如 4K 3840):zoom = 2,整体等比放大,大屏不再留白/字小。
* 缩放倍数被限制在 MIN_SCALE ~ MAX_SCALE 之间,MAX_SCALE=2 恰好覆盖 4K(3840/1920)。
*
* 采用 zoom 挂在 documentElement 上,可覆盖包括 Element UI 挂载到 body 的
* 弹窗/提示/下拉选择在内的所有内容,保证整套界面一致缩放、不塌陷。
*
* 兼容性:
* - zoom:Chrome/Edge/Safari/Opera 全系、Firefox 126+(2024-05 起)、
* 以及国内常见 Chromium 内核浏览器(360/QQ/微信等)均支持;
* - 本方案对不支持 zoom 的旧浏览器(如 2024 前的 Firefox)做能力探测,
* 自动降级为 transform: scale() 回退,保证不崩塌、可正常使用。
*/
let initialized = false
export const DEFAULT_DESIGN_WIDTH = 1920
// 最小缩放倍数:极窄视口的安全下限,避免文字过小/布局异常
export const MIN_SCALE = 0.5
// 最大缩放倍数:2 对应 4K(3840/1920=2),更高分辨率也封顶在此,避免过度放大
export const MAX_SCALE = 2
/**
* 探测当前浏览器是否支持 CSS zoom 属性。
* 现代 Chromium/Safari/Firefox(≥126) 返回 true;旧 Firefox(<126) 返回 false。
* @returns {boolean}
*/
function isZoomSupported() {
const el = document.createElement('div')
el.style.zoom = '0.5'
// 能读回 "0.5" 说明浏览器真正支持
return el.style.zoom === '0.5'
}
/**
* 初始化全局自适应。可在任意入口调用,重复调用幂等。
* @param {number} [designWidth] 设计基准宽度,默认 1920
*/
export function initAdaptive(designWidth = DEFAULT_DESIGN_WIDTH) {
if (initialized) {
return
}
initialized = true
const useZoom = isZoomSupported()
// rAF 帧对齐节流:把同一帧内多次 resize 合并为一次
let rafId = null
// 真实写入节流 + 尾随兜底:拖拽/设备模拟期间 resize 高频触发,
// 若每帧都写 html.zoom 会强制整页重排+重绘导致卡顿,因此限制真实写入频率
let lastAppliedScale = -1
let lastWriteAt = 0
let trailingTimer = null
const WRITE_MIN_INTERVAL = 100 // ms
function computeScale() {
const root = document.documentElement
const width = window.innerWidth || (root && root.clientWidth) || designWidth
const ratio = width / designWidth
// 收敛到 [MIN_SCALE, MAX_SCALE],规避极端宽高的异常比例
return Math.min(MAX_SCALE, Math.max(MIN_SCALE, ratio))
}
function writeScale(scale) {
const root = document.documentElement
if (!root) {
return
}
// 值未变化:跳过,避免冗余的样式失效/整页重排
if (Math.abs(scale - lastAppliedScale) < 1e-4) {
return
}
lastAppliedScale = scale
if (scale === 1) {
clearScale(root)
return
}
if (useZoom) {
// 首选:zoom 参与布局计算,视觉与占位一致,最平滑
root.style.zoom = String(scale)
// 注入缩放与逆缩放变量:逆缩放用于把挂到 body 的 Element 浮层还原为 1:1,
// 以抵消 zoom 对 getBoundingClientRect 坐标造成的二次缩放(否则下拉框位置错乱)
root.style.setProperty('--adaptive-scale', String(scale))
root.style.setProperty('--adaptive-scale-inv', String(1 / scale))
} else {
// 回退:transform 仅影响视觉;将布局宽度固定为设计宽并从左上角缩放,
// 使其铺满视口且不改变内部结构,旧浏览器也能正常使用
root.style.transformOrigin = 'top left'
root.style.width = `${designWidth}px`
root.style.transform = `scale(${scale})`
}
}
function clearScale(root) {
if (useZoom) {
root.style.zoom = ''
root.style.removeProperty('--adaptive-scale')
root.style.removeProperty('--adaptive-scale-inv')
} else {
root.style.transform = ''
root.style.transformOrigin = ''
root.style.width = ''
}
}
/** 立即计算并写入一次(带值变化守卫) */
function applyNow() {
lastWriteAt = Date.now()
writeScale(computeScale())
}
function onResize() {
if (document.hidden) {
return // 后台标签不执行,避免无谓开销
}
if (rafId) {
return // 本帧内已有待执行任务,合并
}
rafId = window.requestAnimationFrame(() => {
rafId = null
if (document.hidden) {
return
}
const now = Date.now()
if (trailingTimer) {
clearTimeout(trailingTimer)
}
// 距上次真实写入已超过阈值才立即写入,其余情况靠尾随兜底,避免满帧整页重排
if (now - lastWriteAt >= WRITE_MIN_INTERVAL) {
applyNow()
}
// 尾随:拖拽/改变尺寸停下后,确保落到最终窗口尺寸
trailingTimer = setTimeout(applyNow, WRITE_MIN_INTERVAL)
})
}
function onVisibilityChange() {
if (!document.hidden) {
// 从后台切回时补算一次
if (trailingTimer) {
clearTimeout(trailingTimer)
}
applyNow()
}
}
// 首次:渲染前同步应用一次,避免首帧闪烁
applyNow()
window.addEventListener('resize', onResize)
// 从后台标签切回来时,若期间窗口尺寸变化(因 hidden 被跳过)需补算一次
document.addEventListener('visibilitychange', onVisibilityChange)
}
-13
View File
@@ -1,13 +0,0 @@
import store from '@/store'
import defaultSettings from '@/settings'
/**
* 动态修改标题
*/
export function useDynamicTitle() {
if (store.state.settings.dynamicTitle) {
document.title = store.state.settings.title + ' - ' + defaultSettings.title
} else {
document.title = defaultSettings.title
}
}
-881
View File
@@ -1,881 +0,0 @@
<template>
<div class="app-container assessment-page">
<el-tabs v-model="activeTab" type="border-card" class="assessment-tabs">
<!-- ==================== Tab1:课程表信息列表 ==================== -->
<el-tab-pane label="课程表信息列表" name="schedule">
<!-- 查询条件 -->
<el-card shadow="never" class="search-card">
<el-form :model="scheduleSearchForm" label-width="130px" class="search-form">
<!-- 考核日期:独立整行 -->
<el-row :gutter="0">
<el-col :span="24">
<el-form-item>
<template slot="label">
<el-checkbox v-model="scheduleSearchForm.examDateEnabled">考核日期</el-checkbox>
</template>
<div class="date-block">
<div class="date-range">
<span>从</span>
<el-date-picker
v-model="scheduleSearchForm.examDateFrom"
type="date"
value-format="yyyy-MM-dd"
:disabled="!scheduleSearchForm.examDateEnabled"
/>
<span>到</span>
<el-date-picker
v-model="scheduleSearchForm.examDateTo"
type="date"
value-format="yyyy-MM-dd"
:disabled="!scheduleSearchForm.examDateEnabled"
/>
</div>
<div class="quick-row">
<el-select v-model="scheduleSearchForm.semester" class="quick-select">
<el-option v-for="item in semesterOptions" :key="item" :label="item" :value="item" />
</el-select>
<el-button size="small" @click="handleQuickDate('semester')">确定</el-button>
<el-select v-model="scheduleSearchForm.week" class="quick-select">
<el-option v-for="item in weekOptions" :key="item" :label="item" :value="item" />
</el-select>
<el-button size="small" @click="handleQuickDate('week')">确定</el-button>
</div>
</div>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="0">
<!-- 左栏 -->
<el-col :span="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="scheduleSearchForm.deptEnabled">教研室</el-checkbox>
</template>
<el-select v-model="scheduleSearchForm.dept" class="w-full" :disabled="!scheduleSearchForm.deptEnabled">
<el-option v-for="item in deptOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item>
<template slot="label">
<el-checkbox v-model="scheduleSearchForm.buildingEnabled">教学楼</el-checkbox>
</template>
<el-select v-model="scheduleSearchForm.building" class="w-full" :disabled="!scheduleSearchForm.buildingEnabled">
<el-option v-for="item in buildingOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item label="培训层次">
<el-select v-model="scheduleSearchForm.trainLevel" placeholder="请选择" clearable class="w-full">
<el-option v-for="item in trainLevelOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item>
<template slot="label">
<el-checkbox v-model="scheduleSearchForm.deptSystemEnabled">监考教员所属院部系</el-checkbox>
</template>
<el-select v-model="scheduleSearchForm.deptSystem" class="w-full" :disabled="!scheduleSearchForm.deptSystemEnabled">
<el-option v-for="item in deptOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item>
<template slot="label">
<el-checkbox v-model="scheduleSearchForm.resourceTypeEnabled">保障资源类别</el-checkbox>
</template>
<el-select v-model="scheduleSearchForm.resourceType" class="w-full" :disabled="!scheduleSearchForm.resourceTypeEnabled">
<el-option v-for="item in resourceTypeOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item>
<template slot="label">
<el-checkbox v-model="scheduleSearchForm.periodEnabled">节次</el-checkbox>
</template>
<el-select v-model="scheduleSearchForm.period" class="w-full" :disabled="!scheduleSearchForm.periodEnabled">
<el-option v-for="item in periodOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item>
<template slot="label">
<el-checkbox v-model="scheduleSearchForm.combineEnabled">合堂选项</el-checkbox>
</template>
<el-checkbox v-model="scheduleSearchForm.combineChange" :disabled="!scheduleSearchForm.combineEnabled">合堂变动</el-checkbox>
</el-form-item>
<el-form-item label="专业名称">
<el-input v-model="scheduleSearchForm.majorName" placeholder="请输入" clearable />
</el-form-item>
<el-form-item label="监考教员姓名">
<el-input v-model="scheduleSearchForm.invigilatorName" placeholder="请输入" clearable />
</el-form-item>
<el-form-item label="考核场地名称">
<el-input v-model="scheduleSearchForm.examPlaceName" placeholder="请输入" clearable />
</el-form-item>
</el-col>
<!-- 右栏 -->
<el-col :span="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="scheduleSearchForm.invigilatorEnabled">监考教员</el-checkbox>
</template>
<el-select v-model="scheduleSearchForm.invigilator" class="w-full" :disabled="!scheduleSearchForm.invigilatorEnabled">
<el-option v-for="item in invigilatorOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item>
<template slot="label">
<el-checkbox v-model="scheduleSearchForm.examSubjectEnabled">考核科目</el-checkbox>
</template>
<el-select v-model="scheduleSearchForm.examSubject" class="w-full" :disabled="!scheduleSearchForm.examSubjectEnabled">
<el-option v-for="item in subjectOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item>
<template slot="label">
<el-checkbox v-model="scheduleSearchForm.placeEnabled">教学场地</el-checkbox>
</template>
<el-select v-model="scheduleSearchForm.place" class="w-full" :disabled="!scheduleSearchForm.placeEnabled">
<el-option v-for="item in placeOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item>
<template slot="label">
<el-checkbox v-model="scheduleSearchForm.teamEnabled">班次</el-checkbox>
</template>
<div class="team-field">
<el-select v-model="scheduleSearchForm.team" placeholder="请选择" :disabled="!scheduleSearchForm.teamEnabled">
<el-option v-for="item in teamOptions" :key="item" :label="item" :value="item" />
</el-select>
<el-checkbox v-model="scheduleSearchForm.showElective" class="elective-check">显示选修课临时班</el-checkbox>
</div>
</el-form-item>
<el-form-item>
<template slot="label">
<el-checkbox v-model="scheduleSearchForm.teamCategoryEnabled">队别</el-checkbox>
</template>
<el-select v-model="scheduleSearchForm.teamCategory" class="w-full" :disabled="!scheduleSearchForm.teamCategoryEnabled">
<el-option v-for="item in teamCategoryOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item>
<template slot="label">
<el-checkbox v-model="scheduleSearchForm.resourceEnabled">保障资源</el-checkbox>
</template>
<el-select v-model="scheduleSearchForm.resource" class="w-full" :disabled="!scheduleSearchForm.resourceEnabled">
<el-option v-for="item in resourceOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item>
<template slot="label">
<el-checkbox v-model="scheduleSearchForm.manageOrgEnabled">管理机构</el-checkbox>
</template>
<el-select v-model="scheduleSearchForm.manageOrg" class="w-full" :disabled="!scheduleSearchForm.manageOrgEnabled">
<el-option v-for="item in manageOrgOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item>
<template slot="label">
<el-checkbox v-model="scheduleSearchForm.logOptionEnabled">日志填写选项</el-checkbox>
</template>
<el-radio-group v-model="scheduleSearchForm.logOption" :disabled="!scheduleSearchForm.logOptionEnabled">
<el-radio :label="'已填写'">已填写</el-radio>
<el-radio :label="'未填写'">未填写</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="年级">
<el-input v-model="scheduleSearchForm.gradeInput" placeholder="请输入" clearable />
</el-form-item>
<el-form-item label="队别班次名称">
<el-input v-model="scheduleSearchForm.teamClassName" placeholder="请输入" clearable />
</el-form-item>
<el-form-item label="科目名称">
<el-input v-model="scheduleSearchForm.subjectName" placeholder="请输入" clearable />
</el-form-item>
</el-col>
</el-row>
<div class="search-tip">注意:勾选"选择框"表示启用该项对应的查询条件。</div>
<div class="search-actions">
<el-button type="primary" @click="handleScheduleQuery">查询</el-button>
</div>
</el-form>
</el-card>
<!-- 操作与列表区域 -->
<el-card shadow="never" class="table-card">
<div class="table-title">课程表信息列表</div>
<div class="action-bar">
<div class="action-row">
<el-button type="primary" size="small" @click="handleScheduleBatch('批量设置备注信息')">批量设置备注信息</el-button>
<el-button type="primary" size="small" @click="handleScheduleBatch('批量变更时间')">批量变更时间</el-button>
<el-button type="primary" size="small" @click="handleScheduleBatch('批量平移时间')">批量平移时间</el-button>
<el-button type="primary" size="small" @click="handleScheduleBatch('批量变更授课教员')">批量变更授课教员</el-button>
<el-button type="primary" size="small" @click="handleScheduleBatch('批量变更授课场地')">批量变更授课场地</el-button>
<el-button type="primary" size="small" @click="handleExport('班次课程课时汇总')">班次课程课时汇总另存</el-button>
<el-button type="primary" size="small" @click="handleExport('教学实施计划概要')">教学实施计划概要另存</el-button>
<el-button type="primary" size="small" @click="handleExport('教学实施计划')">教学实施计划另存</el-button>
</div>
<div class="action-row">
<el-button type="danger" size="small" @click="handleScheduleBatchDelete">批量删除</el-button>
</div>
</div>
<el-table v-loading="scheduleLoading" :data="schedulePagedData" border stripe class="main-table" @selection-change="handleScheduleSelectionChange">
<el-table-column type="selection" width="50" />
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="time" label="上课时间" width="150" align="center" />
<el-table-column prop="course" label="课程" width="110" show-overflow-tooltip />
<el-table-column prop="deptUnit" label="责任单位" width="120" show-overflow-tooltip />
<el-table-column prop="teamCategory" label="队别" width="90" align="center" />
<el-table-column prop="teamClass" label="班次" show-overflow-tooltip />
<el-table-column prop="grade" label="年级" width="90" align="center" />
<el-table-column prop="major" label="专业" width="100" show-overflow-tooltip />
<el-table-column prop="teacher" label="教员" width="80" align="center" />
<el-table-column prop="countPlace" label="人数/场地" width="110" align="center" />
<el-table-column prop="contentMethod" label="教学内容方法" width="110" show-overflow-tooltip />
<el-table-column prop="keyPoints" label="教学要点" width="100" show-overflow-tooltip />
<el-table-column prop="guarantee" label="保障明细" width="110" show-overflow-tooltip />
<el-table-column prop="remark" label="备注" width="90" show-overflow-tooltip />
</el-table>
<div class="pagination-wrap">
<el-pagination
background
layout="total, prev, pager, next, jumper"
:total="scheduleData.length"
:page-size="schedulePageSize"
:current-page="scheduleCurrentPage"
@current-change="handleSchedulePageChange"
/>
</div>
</el-card>
</el-tab-pane>
<!-- ==================== Tab2:课程检索 ==================== -->
<el-tab-pane label="课程检索" name="search">
<div v-if="statusTip" class="status-tip">{{ statusTip }}</div>
<div class="big-title">2026年秋季学期-课程检索</div>
<div class="export-bar">
<el-button type="primary" @click="handleExport('另存Excel(成绩五级制)')">另存Excel(成绩五级制)</el-button>
<el-button type="primary" @click="handleExport('另存Excel(成绩四级制)')">另存Excel(成绩四级制)</el-button>
<el-button type="primary" @click="handleExport('课程学员名单')">课程学员名单</el-button>
</div>
<!-- 查询条件 -->
<el-card shadow="never" class="search-card">
<el-form :model="courseSearchForm" label-width="130px" class="search-form">
<el-row :gutter="0">
<!-- 左栏 -->
<el-col :span="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="courseSearchForm.deptEnabled">责任教研室</el-checkbox>
</template>
<el-select v-model="courseSearchForm.dept" class="w-full" :disabled="!courseSearchForm.deptEnabled">
<el-option v-for="item in deptOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item>
<template slot="label">
<el-checkbox v-model="courseSearchForm.courseTypeEnabled">课类型</el-checkbox>
</template>
<el-select v-model="courseSearchForm.courseType" class="w-full" :disabled="!courseSearchForm.courseTypeEnabled">
<el-option v-for="item in courseTypeOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item label="培训层次">
<el-select v-model="courseSearchForm.trainLevel" placeholder="请选择" clearable class="w-full">
<el-option v-for="item in trainLevelOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item>
<template slot="label">
<el-checkbox v-model="courseSearchForm.teamEnabled">队别(班次)</el-checkbox>
</template>
<el-select v-model="courseSearchForm.team" class="w-full" :disabled="!courseSearchForm.teamEnabled">
<el-option v-for="item in teamOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item label="学分">
<div class="range-control">
<el-input v-model="courseSearchForm.creditMin" placeholder="0" />
<span class="range-sep">至</span>
<el-input v-model="courseSearchForm.creditMax" placeholder="0" />
</div>
</el-form-item>
<el-form-item label="计划学时">
<div class="range-control">
<el-input v-model="courseSearchForm.planHoursMin" placeholder="0" />
<span class="range-sep">至</span>
<el-input v-model="courseSearchForm.planHoursMax" placeholder="0" />
</div>
</el-form-item>
<el-form-item>
<template slot="label">
<el-checkbox v-model="courseSearchForm.arrangeStatusEnabled">考核安排状态</el-checkbox>
</template>
<el-radio-group v-model="courseSearchForm.arrangeStatus" :disabled="!courseSearchForm.arrangeStatusEnabled">
<el-radio :label="'已安排'">已安排</el-radio>
<el-radio :label="'未安排'">未安排</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="教员">
<el-input v-model="courseSearchForm.teacher" placeholder="请输入" clearable />
</el-form-item>
<el-form-item label="队别班次">
<el-input v-model="courseSearchForm.teamClass" placeholder="请输入" clearable />
</el-form-item>
<el-form-item>
<template slot="label">
<el-checkbox v-model="courseSearchForm.scoreStatusEnabled">成绩录入状态</el-checkbox>
</template>
<el-select v-model="courseSearchForm.scoreStatus" class="w-full" :disabled="!courseSearchForm.scoreStatusEnabled">
<el-option v-for="item in scoreStatusOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item label="形成性成绩比例">
<div class="range-control">
<el-input v-model="courseSearchForm.formaScoreMin" placeholder="0" />
<span class="range-suffix">%</span>
<span class="range-sep">至</span>
<el-input v-model="courseSearchForm.formaScoreMax" placeholder="0" />
<span class="range-suffix">%</span>
</div>
</el-form-item>
</el-col>
<!-- 右栏 -->
<el-col :span="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="courseSearchForm.implTeacherEnabled">实施教员</el-checkbox>
</template>
<el-select v-model="courseSearchForm.implTeacher" class="w-full" :disabled="!courseSearchForm.implTeacherEnabled">
<el-option v-for="item in implTeacherOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item>
<template slot="label">
<el-checkbox v-model="courseSearchForm.courseSubjectEnabled">课程科目</el-checkbox>
</template>
<el-select v-model="courseSearchForm.courseSubject" class="w-full" :disabled="!courseSearchForm.courseSubjectEnabled">
<el-option v-for="item in subjectOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item label="教学楼">
<el-select v-model="courseSearchForm.building" class="w-full">
<el-option v-for="item in buildingOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item>
<template slot="label">
<el-checkbox v-model="courseSearchForm.placeEnabled">教学场地</el-checkbox>
</template>
<el-select v-model="courseSearchForm.place" class="w-full" :disabled="!courseSearchForm.placeEnabled">
<el-option v-for="item in placeOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item>
<template slot="label">
<el-checkbox v-model="courseSearchForm.startDateEnabled">开课日期</el-checkbox>
</template>
<div class="range-control">
<el-date-picker v-model="courseSearchForm.startDateStart" type="date" value-format="yyyy-MM-dd" :disabled="!courseSearchForm.startDateEnabled" />
<span class="range-sep">至</span>
<el-date-picker v-model="courseSearchForm.startDateEnd" type="date" value-format="yyyy-MM-dd" :disabled="!courseSearchForm.startDateEnabled" />
</div>
</el-form-item>
<el-form-item>
<template slot="label">
<el-checkbox v-model="courseSearchForm.endDateEnabled">结课日期</el-checkbox>
</template>
<div class="range-control">
<el-date-picker v-model="courseSearchForm.endDateStart" type="date" value-format="yyyy-MM-dd" :disabled="!courseSearchForm.endDateEnabled" />
<span class="range-sep">至</span>
<el-date-picker v-model="courseSearchForm.endDateEnd" type="date" value-format="yyyy-MM-dd" :disabled="!courseSearchForm.endDateEnabled" />
</div>
</el-form-item>
<el-form-item>
<template slot="label">
<el-checkbox v-model="courseSearchForm.examDateEnabled">考试日期</el-checkbox>
</template>
<div class="range-control">
<el-date-picker v-model="courseSearchForm.examDateStart" type="date" value-format="yyyy-MM-dd" :disabled="!courseSearchForm.examDateEnabled" />
<span class="range-sep">至</span>
<el-date-picker v-model="courseSearchForm.examDateEnd" type="date" value-format="yyyy-MM-dd" :disabled="!courseSearchForm.examDateEnabled" />
</div>
</el-form-item>
<el-form-item label="课程科目">
<el-input v-model="courseSearchForm.courseSubjectInput" placeholder="请输入" clearable />
</el-form-item>
<el-form-item label="教学场地">
<el-input v-model="courseSearchForm.placeInput" placeholder="请输入" clearable />
</el-form-item>
<el-form-item>
<template slot="label">
<el-checkbox v-model="courseSearchForm.finalStrategyEnabled">终结性成绩策略</el-checkbox>
</template>
<el-checkbox v-model="courseSearchForm.finalPassEnabled">终结性成绩定及格</el-checkbox>
</el-form-item>
<el-form-item>
<template slot="label">
<el-checkbox v-model="courseSearchForm.manageOrgEnabled">管理机构</el-checkbox>
</template>
<el-select v-model="courseSearchForm.manageOrg" class="w-full" :disabled="!courseSearchForm.manageOrgEnabled">
<el-option v-for="item in manageOrgOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<div class="search-tip">注意:勾选"选择框"表示启用该项对应的查询条件,输入框非空表示启用查询条件。</div>
<div class="search-actions">
<el-button type="primary" @click="handleCourseQuery">查询</el-button>
</div>
</el-form>
</el-card>
<!-- 数据表格 -->
<el-card shadow="never" class="table-card">
<el-table v-loading="courseLoading" :data="coursePagedData" border stripe class="main-table">
<el-table-column type="index" label="序号" width="50" align="center" />
<el-table-column label="课程名称/课时系数" width="150" align="center">
<template slot="header">
<div class="two-line-header">
<span>课程名称</span>
<span>课时系数</span>
</div>
</template>
<template slot-scope="{ row }">
<div class="two-line-cell">
<span>{{ row.courseName }}</span>
<span>{{ row.coeff }}</span>
</div>
</template>
</el-table-column>
<el-table-column prop="teacherTimes" label="责任教员&课次" width="120" align="center" />
<el-table-column prop="teamClass" label="班次" show-overflow-tooltip />
<el-table-column prop="grade" label="年级" width="90" align="center" />
<el-table-column prop="major" label="专业" width="120" show-overflow-tooltip />
<el-table-column prop="planHours" label="计划学时" width="90" align="center" />
<el-table-column prop="runHours" label="运行学时" width="90" align="center" />
<el-table-column prop="examType" label="考核类型方式" width="110" align="center" />
<el-table-column prop="implTeacher" label="实施教员" width="90" align="center" />
<el-table-column prop="countPlace" label="人数/场地" width="110" align="center" />
<el-table-column prop="courseLecture" label="课程讲座" width="90" align="center" />
<el-table-column prop="planChange" label="实施计划变" width="90" align="center" />
</el-table>
<div class="pagination-wrap">
<el-pagination
background
layout="total, prev, pager, next, jumper"
:total="courseData.length"
:page-size="coursePageSize"
:current-page="courseCurrentPage"
@current-change="handleCoursePageChange"
/>
</div>
</el-card>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
export default {
name: 'AssessmentIndex',
data() {
return {
activeTab: 'schedule',
// ==================== 下拉选项(两页共用) ====================
deptOptions: ['教务处', '政治工作教研室', '军事基础教研室', '装备保障教研室'],
trainLevelOptions: ['本科', '研究生', '大专'],
buildingOptions: ['校内', '教学楼A', '教学楼B'],
placeOptions: ['操场', '教室101', '实验室203'],
subjectOptions: ['英语(补课)', '高等数学', '军事理论', '战术基础'],
teamOptions: ['2025级炮兵装备运用(线膛炮)专业学员54队5区队', '2024级学员32队', '研究生1队'],
manageOrgOptions: ['教务处', '教务科', '教研室'],
// Tab1 专属选项
semesterOptions: ['2026年春季学期', '2026年秋季学期', '2025年秋季学期'],
weekOptions: ['第20周2026-07-06', '第19周2026-06-29', '第21周2026-07-13'],
resourceTypeOptions: ['弹药', '器材', '场地'],
periodOptions: ['1-2', '3-4', '5-6', '7-8'],
invigilatorOptions: ['教务处', '张三', '李四', '王五'],
teamCategoryOptions: ['学员大队', '学员一队', '学员二队'],
resourceOptions: ['92式手榴弹', '模拟器材', '靶场'],
// Tab2 专属选项
courseTypeOptions: ['讲座', '讲授', '研讨', '实操'],
scoreStatusOptions: ['未填写', '已填写'],
implTeacherOptions: ['教务处', '张三', '李四', '王五', '赵六'],
// ==================== Tab1 查询条件 ====================
scheduleSearchForm: {
examDateEnabled: true,
examDateFrom: '2026-07-06',
examDateTo: '2026-07-12',
semester: '2026年春季学期',
week: '第20周2026-07-06',
deptEnabled: false,
dept: '教务处',
buildingEnabled: true,
building: '校内',
trainLevel: '',
deptSystemEnabled: false,
deptSystem: '教务处',
resourceTypeEnabled: false,
resourceType: '弹药',
periodEnabled: false,
period: '1-2',
combineEnabled: false,
combineChange: false,
majorName: '',
invigilatorName: '',
examPlaceName: '',
invigilatorEnabled: false,
invigilator: '教务处',
examSubjectEnabled: false,
examSubject: '英语(补课)',
placeEnabled: false,
place: '操场',
teamEnabled: false,
team: '',
showElective: true,
teamCategoryEnabled: false,
teamCategory: '学员大队',
resourceEnabled: false,
resource: '92式手榴弹',
manageOrgEnabled: false,
manageOrg: '教务处',
logOptionEnabled: false,
logOption: '未填写',
gradeInput: '',
teamClassName: '',
subjectName: ''
},
// ==================== Tab2 查询条件 ====================
statusTip: '',
courseSearchForm: {
deptEnabled: false,
dept: '教务处',
courseTypeEnabled: false,
courseType: '讲座',
trainLevel: '',
teamEnabled: false,
team: '2025级炮兵装备运用(线膛炮)专业学员54队5区队',
creditMin: '',
creditMax: '',
planHoursMin: '',
planHoursMax: '',
arrangeStatusEnabled: true,
arrangeStatus: '已安排',
teacher: '',
teamClass: '',
scoreStatusEnabled: false,
scoreStatus: '未填写',
formaScoreMin: '',
formaScoreMax: '',
implTeacherEnabled: false,
implTeacher: '教务处',
courseSubjectEnabled: false,
courseSubject: '英语(补课)',
building: '校内',
placeEnabled: false,
place: '操场',
startDateEnabled: false,
startDateStart: '2026-08-21',
startDateEnd: '2027-01-23',
endDateEnabled: false,
endDateStart: '2026-08-21',
endDateEnd: '2027-01-23',
examDateEnabled: false,
examDateStart: '2026-08-21',
examDateEnd: '2027-01-23',
courseSubjectInput: '',
placeInput: '',
finalStrategyEnabled: false,
finalPassEnabled: false,
manageOrgEnabled: false,
manageOrg: '教务处'
},
// ==================== Tab1 表格数据(mock) ====================
scheduleLoading: false,
scheduleData: [
{ id: 1, time: '2026-07-06 08:00-09:40', course: '高等数学', deptUnit: '教务处', teamCategory: '学员大队', teamClass: '2025级本科1班', grade: '2025级', major: '数学', teacher: '张三', countPlace: '45/教室101', contentMethod: '理论讲授', keyPoints: '微积分基础', guarantee: '无', remark: '第1次课' },
{ id: 2, time: '2026-07-06 10:00-11:40', course: '军事理论', deptUnit: '政治工作教研室', teamCategory: '学员大队', teamClass: '2024级本科2班', grade: '2024级', major: '军事', teacher: '李四', countPlace: '38/教室203', contentMethod: '案例教学', keyPoints: '战争理论', guarantee: '多媒体', remark: '' },
{ id: 3, time: '2026-07-07 14:30-16:10', course: '英语(补课)', deptUnit: '军事基础教研室', teamCategory: '学员一队', teamClass: '研究生1班', grade: '2026级', major: '英语', teacher: '王五', countPlace: '30/教室102', contentMethod: '研讨教学', keyPoints: '口语训练', guarantee: '无', remark: '补课' },
{ id: 4, time: '2026-07-07 08:00-09:40', course: '战术基础', deptUnit: '教务处', teamCategory: '学员大队', teamClass: '2023级本科3班', grade: '2023级', major: '战术', teacher: '赵六', countPlace: '50/操场', contentMethod: '实操演练', keyPoints: '战术动作', guarantee: '器材', remark: '' },
{ id: 5, time: '2026-07-08 10:00-11:40', course: '装备保障', deptUnit: '装备保障教研室', teamCategory: '学员二队', teamClass: '研究生2班', grade: '2026级', major: '装备', teacher: '孙七', countPlace: '20/实验室203', contentMethod: '实践教学', keyPoints: '装备维护', guarantee: '模拟器材', remark: '分组' },
{ id: 6, time: '2026-07-08 14:30-16:10', course: '联合作战指挥', deptUnit: '教务处', teamCategory: '学员大队', teamClass: '2025级本科1班', grade: '2025级', major: '指挥', teacher: '李四', countPlace: '45/教室B301', contentMethod: '案例教学', keyPoints: '联合作战', guarantee: '多媒体', remark: '' },
{ id: 7, time: '2026-07-09 08:00-09:40', course: '大学英语', deptUnit: '军事基础教研室', teamCategory: '学员一队', teamClass: '2024级本科2班', grade: '2024级', major: '英语', teacher: '王五', countPlace: '38/教室105', contentMethod: '理论讲授', keyPoints: '语法', guarantee: '无', remark: '' },
{ id: 8, time: '2026-07-09 10:00-11:40', course: '政治理论', deptUnit: '政治工作教研室', teamCategory: '学员大队', teamClass: '研究生1班', grade: '2026级', major: '政治', teacher: '张三', countPlace: '30/教室204', contentMethod: '理论讲授', keyPoints: '形势政策', guarantee: '无', remark: '' },
{ id: 9, time: '2026-07-10 14:30-16:10', course: '装备操作', deptUnit: '装备保障教研室', teamCategory: '学员二队', teamClass: '2023级本科3班', grade: '2023级', major: '装备', teacher: '赵六', countPlace: '45/实验室101', contentMethod: '实操演练', keyPoints: '操作规范', guarantee: '器材', remark: '安全注意' },
{ id: 10, time: '2026-07-10 16:20-18:00', course: '军事地形学', deptUnit: '教务处', teamCategory: '学员大队', teamClass: '2025级本科1班', grade: '2025级', major: '地形', teacher: '孙七', countPlace: '50/操场', contentMethod: '实操演练', keyPoints: '地形判读', guarantee: '地图', remark: '' }
],
selectedRows: [],
scheduleCurrentPage: 1,
schedulePageSize: 20,
// ==================== Tab2 表格数据(mock) ====================
courseLoading: false,
courseData: [
{ id: 1, courseName: '高等数学', coeff: '1.0', teacherTimes: '张三×48', teamClass: '2025级炮兵装备运用(线膛炮)专业学员54队5区队', grade: '2025级', major: '炮兵装备运用', planHours: 96, runHours: 96, examType: '闭卷考试', implTeacher: '张三', countPlace: '45/教室101', courseLecture: '讲座', planChange: '否' },
{ id: 2, courseName: '军事理论', coeff: '1.2', teacherTimes: '李四×32', teamClass: '2024级学员32队', grade: '2024级', major: '军事指挥', planHours: 64, runHours: 62, examType: '开卷考试', implTeacher: '李四', countPlace: '38/教室203', courseLecture: '讲授', planChange: '是' },
{ id: 3, courseName: '英语(补课)', coeff: '0.8', teacherTimes: '王五×40', teamClass: '研究生1队', grade: '2026级', major: '英语', planHours: 80, runHours: 78, examType: '口语测试', implTeacher: '王五', countPlace: '30/教室102', courseLecture: '讲座', planChange: '否' },
{ id: 4, courseName: '战术基础', coeff: '1.0', teacherTimes: '赵六×56', teamClass: '2023级学员51队', grade: '2023级', major: '战术指挥', planHours: 112, runHours: 110, examType: '实操考核', implTeacher: '赵六', countPlace: '50/操场', courseLecture: '实操', planChange: '是' },
{ id: 5, courseName: '装备保障', coeff: '1.1', teacherTimes: '张三×24', teamClass: '研究生2队', grade: '2026级', major: '装备保障', planHours: 48, runHours: 48, examType: '闭卷考试', implTeacher: '孙七', countPlace: '20/实验室203', courseLecture: '讲授', planChange: '否' },
{ id: 6, courseName: '联合作战指挥', coeff: '1.3', teacherTimes: '李四×60', teamClass: '2025级炮兵装备运用(线膛炮)专业学员54队5区队', grade: '2025级', major: '联合作战', planHours: 120, runHours: 118, examType: '综合考核', implTeacher: '李四', countPlace: '45/教室B301', courseLecture: '讲座', planChange: '是' },
{ id: 7, courseName: '大学英语', coeff: '0.8', teacherTimes: '王五×36', teamClass: '2024级学员32队', grade: '2024级', major: '英语', planHours: 72, runHours: 72, examType: '四级制考试', implTeacher: '王五', countPlace: '38/教室105', courseLecture: '讲授', planChange: '否' },
{ id: 8, courseName: '军事地形学', coeff: '1.0', teacherTimes: '赵六×40', teamClass: '2023级学员51队', grade: '2023级', major: '军事指挥', planHours: 80, runHours: 76, examType: '实操考核', implTeacher: '赵六', countPlace: '50/操场', courseLecture: '实操', planChange: '是' },
{ id: 9, courseName: '政治理论', coeff: '1.0', teacherTimes: '张三×48', teamClass: '研究生1队', grade: '2026级', major: '政治学', planHours: 96, runHours: 96, examType: '开卷考试', implTeacher: '孙七', countPlace: '30/教室204', courseLecture: '讲座', planChange: '否' },
{ id: 10, courseName: '装备操作', coeff: '1.2', teacherTimes: '李四×32', teamClass: '2025级炮兵装备运用(线膛炮)专业学员54队5区队', grade: '2025级', major: '炮兵装备运用', planHours: 64, runHours: 62, examType: '实操考核', implTeacher: '李四', countPlace: '45/实验室101', courseLecture: '实操', planChange: '是' }
],
courseCurrentPage: 1,
coursePageSize: 20
}
},
computed: {
schedulePagedData() {
const start = (this.scheduleCurrentPage - 1) * this.schedulePageSize
return this.scheduleData.slice(start, start + this.schedulePageSize)
},
coursePagedData() {
const start = (this.courseCurrentPage - 1) * this.coursePageSize
return this.courseData.slice(start, start + this.coursePageSize)
}
},
methods: {
// ==================== Tab1 查询 ====================
// TODO: 后端接口未提供,以下操作均为前端模拟;接口就绪后替换为对应接口
handleScheduleQuery() {
this.$message.success('查询完成(前端模拟)')
},
/** 学期/周次「确定」快速填充日期范围 */
handleQuickDate(type) {
if (type === 'semester') {
this.$message.success(`已按「${this.scheduleSearchForm.semester}」确定考核日期范围(前端模拟)`)
} else {
this.$message.success(`已按「${this.scheduleSearchForm.week}」确定考核日期范围(前端模拟)`)
}
},
// ==================== 导出按钮 ====================
handleExport(type) {
this.$message.success(`已导出 ${type}(前端模拟)`)
},
// ==================== Tab1 多选与批量操作 ====================
handleScheduleSelectionChange(rows) {
this.selectedRows = rows
},
handleScheduleBatch(action) {
if (this.selectedRows.length === 0) {
this.$message.warning('请先选择记录')
return
}
this.$message.success(`已对所选 ${this.selectedRows.length} 条记录执行「${action}」(前端模拟)`)
},
handleScheduleBatchDelete() {
if (this.selectedRows.length === 0) {
this.$message.warning('请先选择要删除的记录')
return
}
const ids = this.selectedRows.map((r) => r.id)
this.scheduleData = this.scheduleData.filter((r) => ids.indexOf(r.id) === -1)
this.$message.success(`已删除所选 ${ids.length} 条记录(前端模拟)`)
},
handleSchedulePageChange(page) {
this.scheduleCurrentPage = page
},
// ==================== Tab2 查询 ====================
handleCourseQuery() {
const now = new Date()
const hh = String(now.getHours()).padStart(2, '0')
const mm = String(now.getMinutes()).padStart(2, '0')
const ss = String(now.getSeconds()).padStart(2, '0')
const count = this.courseData.length
this.statusTip = `[${hh}:${mm}:${ss}]查询成功! 共检索到${count}条记录。`
this.$message.success(`查询成功,共检索到${count}条记录(前端模拟)`)
},
handleCoursePageChange(page) {
this.courseCurrentPage = page
}
}
}
</script>
<style scoped lang="scss">
.app-container {
padding: 20px;
}
.assessment-page {
.assessment-tabs {
background: #fff;
}
// ==================== 查询条件区域 ====================
.search-card {
margin-bottom: 16px;
.search-form {
.w-full {
width: 100%;
}
.date-block {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
flex-wrap: wrap;
.date-range {
display: flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
}
.quick-row {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
.quick-select {
width: 170px;
}
}
}
.team-field {
display: flex;
align-items: center;
gap: 8px;
.el-select {
flex: 1;
min-width: 0;
}
.elective-check {
white-space: nowrap;
}
}
.range-control {
display: flex;
align-items: center;
gap: 6px;
.el-input {
flex: 1;
}
.el-date-editor {
flex: 1;
min-width: 120px;
}
.range-sep,
.range-suffix {
flex-shrink: 0;
font-size: 12px;
color: #606266;
}
}
.search-tip {
font-size: 12px;
color: #f56c6c;
margin-top: 4px;
margin-bottom: 4px;
}
.search-actions {
display: flex;
justify-content: flex-end;
margin-top: 8px;
padding-top: 12px;
border-top: 1px solid #ebeef5;
}
}
}
// ==================== Tab2 顶部提示 / 标题 / 导出 ====================
.status-tip {
display: inline-block;
background: #dc3545;
color: #fff;
font-size: 12px;
padding: 3px 10px;
border-radius: 4px;
margin-bottom: 12px;
}
.big-title {
text-align: center;
font-size: 20px;
font-weight: 700;
color: #303133;
margin: 8px 0 12px;
}
.export-bar {
display: flex;
justify-content: flex-end;
gap: 12px;
margin-bottom: 16px;
}
// ==================== 数据表格区域 ====================
.table-card {
.table-title {
font-size: 16px;
font-weight: 600;
color: #303133;
margin-bottom: 12px;
}
.action-bar {
.action-row {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 10px;
&:last-child {
margin-bottom: 14px;
}
}
}
.main-table {
::v-deep(.cell) {
font-size: 12px;
}
}
.two-line-header,
.two-line-cell {
display: flex;
flex-direction: column;
line-height: 1.4;
}
.pagination-wrap {
display: flex;
justify-content: flex-end;
margin-top: 12px;
}
}
}
</style>
@@ -4,47 +4,35 @@
<!-- ==================== 1. 查询条件区域 ==================== -->
<el-card shadow="never" class="search-card">
<el-form :model="searchForm" label-width="110px" class="search-form">
<el-row :gutter="0">
<el-col :span="12">
<el-form :model="searchForm" label-width="40px" class="search-form">
<el-row :gutter="6">
<el-col :span="4">
<el-form-item label="名称">
<el-input v-model="searchForm.mc" placeholder="请输入名称" clearable />
</el-form-item>
<el-form-item label="年度">
<el-input v-model="searchForm.nd" placeholder="如 2026" clearable />
<el-input v-model="searchForm.mc" placeholder="请输入名称" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="教员编号">
<el-input v-model="searchForm.jybh" placeholder="如 JY00128" clearable />
</el-form-item>
<el-form-item label="合班类型">
<el-select v-model="searchForm.hflx" placeholder="请选择" clearable class="w-full">
<el-option v-for="item in hflxOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-col :span="2">
<div class="search-actions">
<el-button type="primary" @click="handleQuery">查询</el-button>
</div>
</el-col>
</el-row>
<div class="search-actions">
<el-button type="primary" @click="handleQuery">查询</el-button>
</div>
</el-form>
</el-card>
<!-- ==================== 2. 文件操作区域 ==================== -->
<!-- ==================== 2. 操作区域 ==================== -->
<el-card shadow="never" class="upload-card">
<div class="upload-row">
<div class="upload-left">
<el-button type="primary" @click="handleDownloadTemplate">【联教联训数据文件模板】下载</el-button>
<el-button type="primary" plain @click="handleChooseFile">选择文件</el-button>
<el-button type="primary" @click="handleAdd">新增联教联训</el-button>
<el-button type="primary" plain @click="handleExport">导出 Excel</el-button>
<el-button @click="handleDownloadTemplate">【联教联训数据文件模板】下载</el-button>
<el-button plain @click="handleChooseFile">选择文件</el-button>
<span class="file-name" :class="{ 'has-file': selectedFile }">{{ fileName }}</span>
<input ref="fileInputRef" type="file" style="display: none" @change="handleFileChange" />
</div>
<div class="upload-right">
<el-button type="primary" @click="handleUpload">上传数据</el-button>
<input ref="fileInputRef" type="file" accept=".xls,.xlsx" style="display: none" @change="handleFileChange" />
<el-button type="success" :loading="uploading" @click="handleUpload">上传数据</el-button>
</div>
</div>
<div class="upload-tip">您还没建立 联教联训证明。</div>
</el-card>
<!-- ==================== 3. 数据表格 ==================== -->
@@ -52,20 +40,25 @@
<div class="table-title">联教联训记录列表</div>
<el-table v-loading="tableLoading" :data="tableData" border stripe style="width: 100%">
<el-table-column type="index" label="序号" width="55" align="center" />
<el-table-column prop="bh" label="任务编号" width="150" align="center" show-overflow-tooltip />
<el-table-column prop="mc" label="名称" min-width="160" show-overflow-tooltip />
<el-table-column prop="jybh" label="教员编号" width="100" align="center" />
<el-table-column prop="rwlb" label="任务类别" width="110" align="center" show-overflow-tooltip />
<el-table-column prop="ksl" label="课时量" width="80" align="center" />
<el-table-column label="合班类型" width="90" align="center">
<template slot-scope="{ row }">{{ formatHflx(row.hflx) }}</template>
<el-table-column prop="bh" label="编号" width="150" align="center" show-overflow-tooltip />
<el-table-column prop="mc" label="名称" min-width="140" show-overflow-tooltip />
<el-table-column prop="xz" label="性质" width="80" align="center" show-overflow-tooltip />
<el-table-column prop="yj" label="依据" width="110" align="center" show-overflow-tooltip />
<el-table-column prop="ksrq" label="开始日期" width="110" align="center" :formatter="fmtDateTime" />
<el-table-column prop="jsrq" label="结束日期" width="110" align="center" :formatter="fmtDateTime" />
<el-table-column prop="ts" label="天数" width="70" align="center" />
<el-table-column prop="jbksl" label="基本课时量" width="90" align="center" />
<el-table-column prop="kslx" label="课时类型" width="80" align="center" show-overflow-tooltip />
<el-table-column prop="xmlx" label="项目类型" width="80" align="center" show-overflow-tooltip />
<el-table-column prop="sm" label="说明" min-width="120" show-overflow-tooltip />
<el-table-column prop="bz" label="备注" min-width="120" show-overflow-tooltip />
<el-table-column label="操作" width="160" align="center" fixed="right">
<template slot-scope="{ row }">
<el-button type="text" size="small" @click="handleDetail(row)">详情</el-button>
<el-button type="text" size="small" @click="handleEdit(row)">修改</el-button>
<el-button type="text" size="small" class="text-danger" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
<el-table-column label="是否评优" width="90" align="center">
<template slot-scope="{ row }">{{ formatSfpy(row.sfpy) }}</template>
</el-table-column>
<el-table-column prop="nd" label="年度" width="80" align="center" />
<el-table-column prop="xq" label="学期" width="110" align="center" />
<el-table-column prop="sm" label="说明" min-width="180" show-overflow-tooltip />
</el-table>
<el-pagination
@@ -80,67 +73,122 @@
/>
</el-card>
<!-- ==================== 4. 新增联教联训记录区域 ==================== -->
<el-card shadow="never" class="search-card">
<div class="form-title">新增联教联训记录</div>
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px" class="search-form">
<el-row :gutter="0">
<!-- ==================== 4. 新增/修改对话框 ==================== -->
<el-dialog
:visible="dialogVisible"
:title="dialogTitle"
width="720px"
:close-on-click-modal="false"
@update:visible="val => dialogVisible = val"
>
<el-form ref="formRef" :model="form" :rules="rules" label-width="110px" class="add-form">
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="任务编号" prop="bh">
<el-input v-model="form.bh" placeholder="如 RW20260810001" clearable />
<el-form-item label="编号">
<el-input v-model="form.bh" placeholder="留空则自动生成" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="名称" prop="mc">
<el-input v-model="form.mc" placeholder="请输入名称" clearable />
</el-form-item>
<el-form-item label="教员编号" prop="jybh">
<el-input v-model="form.jybh" placeholder="如 JY00128" clearable />
</el-form-item>
<el-form-item label="任务类别" prop="rwlb">
<el-input v-model="form.rwlb" placeholder="如 理论授课" clearable />
</el-form-item>
<el-form-item label="课时量" prop="ksl">
<el-input v-model="form.ksl" placeholder="如 16.5" clearable />
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="性质">
<el-input v-model="form.xz" placeholder="请输入性质" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="合班类型">
<el-radio-group v-model="form.hflx">
<el-radio :label="0">未合班</el-radio>
<el-radio :label="1">合班</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="是否评优">
<el-radio-group v-model="form.sfpy">
<el-radio :label="0">否</el-radio>
<el-radio :label="1">是</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="年度" prop="nd">
<el-input v-model="form.nd" placeholder="如 2026" clearable />
</el-form-item>
<el-form-item label="学期" prop="xq">
<el-input v-model="form.xq" placeholder="如 2026-2027-1" clearable />
</el-form-item>
<el-form-item label="说明" prop="sm">
<el-input v-model="form.sm" type="textarea" :rows="3" placeholder="请输入说明" />
<el-form-item label="依据">
<el-input v-model="form.yj" placeholder="请输入依据" clearable />
</el-form-item>
</el-col>
</el-row>
<div class="search-actions">
<el-button type="primary" :loading="addLoading" @click="handleAdd">添加</el-button>
</div>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="开始日期">
<el-date-picker v-model="form.ksrq" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" placeholder="请选择开始日期" clearable class="w-full" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="结束日期">
<el-date-picker v-model="form.jsrq" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" placeholder="请选择结束日期" clearable class="w-full" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="天数">
<el-input-number v-model="form.ts" :min="0" :precision="1" :step="1" class="w-full" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="基本课时量">
<el-input-number v-model="form.jbksl" :min="0" :precision="1" :step="0.5" class="w-full" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="课时类型">
<el-input v-model="form.kslx" placeholder="请输入课时类型" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="项目类型">
<el-input v-model="form.xmlx" placeholder="请输入项目类型" clearable />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="说明">
<el-input v-model="form.sm" type="textarea" :rows="2" placeholder="请输入说明" />
</el-form-item>
<el-form-item label="备注">
<el-input v-model="form.bz" type="textarea" :rows="2" placeholder="请输入备注" />
</el-form-item>
</el-form>
</el-card>
<div slot="footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" :loading="saving" @click="handleSubmit">确定</el-button>
</div>
</el-dialog>
<!-- ==================== 5. 详情对话框 ==================== -->
<el-dialog :visible="detailVisible" title="联教联训详情" width="640px" @update:visible="val => detailVisible = val">
<el-descriptions :column="2" border>
<el-descriptions-item label="编号">{{ detailData.bh }}</el-descriptions-item>
<el-descriptions-item label="名称">{{ detailData.mc }}</el-descriptions-item>
<el-descriptions-item label="性质">{{ fmtEmpty(detailData.xz) }}</el-descriptions-item>
<el-descriptions-item label="依据">{{ fmtEmpty(detailData.yj) }}</el-descriptions-item>
<el-descriptions-item label="开始日期">{{ fmtDateTime(null, null, detailData.ksrq) }}</el-descriptions-item>
<el-descriptions-item label="结束日期">{{ fmtDateTime(null, null, detailData.jsrq) }}</el-descriptions-item>
<el-descriptions-item label="天数">{{ fmtValue(detailData.ts) }}</el-descriptions-item>
<el-descriptions-item label="基本课时量">{{ fmtValue(detailData.jbksl) }}</el-descriptions-item>
<el-descriptions-item label="课时类型">{{ fmtEmpty(detailData.kslx) }}</el-descriptions-item>
<el-descriptions-item label="项目类型">{{ fmtEmpty(detailData.xmlx) }}</el-descriptions-item>
<el-descriptions-item label="说明" :span="2">{{ fmtEmpty(detailData.sm) }}</el-descriptions-item>
<el-descriptions-item label="备注" :span="2">{{ fmtEmpty(detailData.bz) }}</el-descriptions-item>
</el-descriptions>
<div slot="footer">
<el-button @click="detailVisible = false">关闭</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
// 模拟列表数据(后端接口未提供,接口就绪后可删除并改为接口加载)
const mockTableData = [
{ bh: 'RW20260810001', mc: '某型装备联合训练', jybh: 'JY00128', rwlb: '理论授课', ksl: 16.5, hflx: 1, sfpy: 1, nd: '2026', xq: '2026-2027-1', sm: '' },
{ bh: 'RW20260810002', mc: '通信保障联合演练', jybh: 'JY00302', rwlb: '实践教学', ksl: 24, hflx: 0, sfpy: 0, nd: '2026', xq: '2026-2027-1', sm: '跨单位合练' },
{ bh: 'RW20260721003', mc: '联合教学研讨', jybh: 'JY00511', rwlb: '教学研究', ksl: 8, hflx: 1, sfpy: 1, nd: '2025', xq: '2025-2026-2', sm: '' }
]
import { saveAs } from 'file-saver'
import {
listJointTraining,
addJointTraining,
updateJointTraining,
deleteJointTraining,
getJointTraining,
importJointTraining,
exportJointTraining
} from '@/api/classHour/jointTraining'
export default {
name: 'JointTrainingIndex',
@@ -148,49 +196,32 @@ export default {
return {
// ==================== 1. 查询条件 ====================
searchForm: {
mc: '',
jybh: '',
nd: '',
hflx: ''
mc: ''
},
hflxOptions: [
{ label: '未合班', value: '0' },
{ label: '合班', value: '1' }
],
// ==================== 2. 文件操作 ====================
selectedFile: null,
uploading: false,
// ==================== 3. 表格数据 ====================
tableLoading: false,
tableData: [],
allData: [],
pageNum: 1,
pageSize: 10,
total: 0,
// ==================== 4. 新增联教联训记录表单 ====================
form: {
bh: this.genBh(),
mc: '',
jybh: '',
rwlb: '',
ksl: '',
hflx: 0,
sfpy: 0,
nd: '2026',
xq: '2026-2027-1',
sm: ''
},
// ==================== 4. 新增/修改表单 ====================
dialogVisible: false,
dialogTitle: '新增联教联训',
saving: false,
form: this.createEmptyForm(),
rules: {
bh: [{ required: true, message: '请输入任务编号', trigger: 'blur' }],
mc: [{ required: true, message: '请输入名称', trigger: 'blur' }],
jybh: [{ required: true, message: '请输入教员编号', trigger: 'blur' }],
rwlb: [{ required: true, message: '请输入任务类别', trigger: 'blur' }],
ksl: [{ required: true, message: '请输入课时量', trigger: 'blur' }],
nd: [{ required: true, message: '请输入年度', trigger: 'blur' }]
mc: [{ required: true, message: '请输入名称', trigger: 'blur' }]
},
addLoading: false
// ==================== 5. 详情 ====================
detailVisible: false,
detailData: {}
}
},
computed: {
@@ -199,73 +230,177 @@ export default {
}
},
mounted() {
this.handleQuery()
this.fetchList()
},
methods: {
/** 生成任务编号(RW+日期+序号) */
genBh() {
const d = new Date()
const ymd = '' + d.getFullYear() + (d.getMonth() + 1 < 10 ? '0' + (d.getMonth() + 1) : d.getMonth() + 1) + (d.getDate() < 10 ? '0' + d.getDate() : d.getDate())
const seq = String(Math.floor(Math.random() * 900) + 100)
return `RW${ymd}${seq}`
createEmptyForm() {
return {
bh: '',
mc: '',
xz: '',
yj: '',
ksrq: '',
jsrq: '',
ts: null,
jbksl: null,
kslx: '',
xmlx: '',
sm: '',
bz: ''
}
},
/** 把后端 LocalDateTime(含 T/毫秒)规范为表单可回显的格式 */
normalizeDateTime(val) {
if (!val) return ''
return String(val).replace('T', ' ').replace(/\.\d+$/, '').slice(0, 19)
},
// ==================== 查询 ====================
// TODO: 后端接口未提供,暂用模拟数据;接口就绪后替换为 getJointTrainingList
handleQuery() {
fetchList() {
this.tableLoading = true
setTimeout(() => {
let records = mockTableData.slice()
if (this.searchForm.mc.trim()) {
records = records.filter((item) => (item.mc || '').includes(this.searchForm.mc.trim()))
}
if (this.searchForm.jybh.trim()) {
records = records.filter((item) => (item.jybh || '').includes(this.searchForm.jybh.trim()))
}
if (this.searchForm.nd.trim()) {
records = records.filter((item) => String(item.nd || '').includes(this.searchForm.nd.trim()))
}
if (this.searchForm.hflx !== '') {
records = records.filter((item) => String(item.hflx) === this.searchForm.hflx)
}
this.allData = records
this.total = records.length
this.pageNum = 1
this.applyPage()
this.$message.success(`查询完成,共 ${records.length} 条记录(前端模拟)`)
const params = { pageNum: this.pageNum, pageSize: this.pageSize }
if (this.searchForm.mc && this.searchForm.mc.trim()) params.mc = this.searchForm.mc.trim()
listJointTraining(params).then(res => {
const data = (res && res.data) || {}
this.tableData = data.records || []
this.total = data.total || 0
this.tableLoading = false
}, 200)
}).catch(() => {
this.tableData = []
this.total = 0
this.tableLoading = false
})
},
applyPage() {
const start = (this.pageNum - 1) * this.pageSize
this.tableData = this.allData.slice(start, start + this.pageSize)
handleQuery() {
this.pageNum = 1
this.fetchList()
},
handlePageChange(page) {
this.pageNum = page
this.applyPage()
this.fetchList()
},
handleSizeChange(size) {
this.pageSize = size
this.pageNum = 1
this.applyPage()
this.fetchList()
},
/** 合班类型显示 */
formatHflx(val) {
if (val === undefined || val === null || val === '') return '-'
return String(val) === '1' ? '合班' : '未合班'
/** 日期时间显示(去掉 T、截断到分钟) */
fmtDateTime(row, column, cellValue) {
if (cellValue === null || cellValue === undefined || cellValue === '') return '-'
return String(cellValue).replace('T', ' ').slice(0, 16)
},
/** 是否评优显示 */
formatSfpy(val) {
if (val === undefined || val === null || val === '') return '-'
return String(val) === '1' ? '是' : '否'
/** 空值显示为 '-' */
fmtEmpty(val) {
return val === null || val === undefined || val === '' ? '-' : val
},
// ==================== 2. 文件操作 ====================
/** 数值空值显示为 '-'(保留 0) */
fmtValue(val) {
return val === null || val === undefined ? '-' : val
},
// ==================== 新增 / 修改 ====================
handleAdd() {
this.form = this.createEmptyForm()
this.dialogTitle = '新增联教联训'
this.dialogVisible = true
},
handleEdit(row) {
getJointTraining(row.bh).then(res => {
const d = (res && res.data) || {}
this.form = {
bh: d.bh || '',
mc: d.mc || '',
xz: d.xz || '',
yj: d.yj || '',
ksrq: this.normalizeDateTime(d.ksrq),
jsrq: this.normalizeDateTime(d.jsrq),
ts: d.ts === null || d.ts === undefined ? null : d.ts,
jbksl: d.jbksl === null || d.jbksl === undefined ? null : d.jbksl,
kslx: d.kslx || '',
xmlx: d.xmlx || '',
sm: d.sm || '',
bz: d.bz || ''
}
this.dialogTitle = '修改联教联训'
this.dialogVisible = true
}).catch(() => {})
},
handleSubmit() {
this.$refs.formRef.validate(valid => {
if (!valid) {
this.$message.warning('请完善必填项后再提交')
return
}
const payload = this.buildPayload()
this.saving = true
const req = payload.bh ? updateJointTraining(payload) : addJointTraining(payload)
req.then(() => {
this.$message.success(this.dialogTitle === '修改联教联训' ? '修改成功' : '新增成功')
this.dialogVisible = false
this.fetchList()
}).catch(() => {}).finally(() => {
this.saving = false
})
})
},
/** 构建请求体,仅含实体非空字段(创建/修改时间为后端自动维护,不传) */
buildPayload() {
const f = this.form
const payload = {}
if (f.bh && f.bh.trim()) payload.bh = f.bh.trim()
if (f.mc && f.mc.trim()) payload.mc = f.mc.trim()
if (f.xz && f.xz.trim()) payload.xz = f.xz.trim()
if (f.yj && f.yj.trim()) payload.yj = f.yj.trim()
if (f.ksrq) payload.ksrq = f.ksrq
if (f.jsrq) payload.jsrq = f.jsrq
if (f.ts !== null && f.ts !== undefined) payload.ts = f.ts
if (f.jbksl !== null && f.jbksl !== undefined) payload.jbksl = f.jbksl
if (f.kslx && f.kslx.trim()) payload.kslx = f.kslx.trim()
if (f.xmlx && f.xmlx.trim()) payload.xmlx = f.xmlx.trim()
if (f.sm && f.sm.trim()) payload.sm = f.sm.trim()
if (f.bz && f.bz.trim()) payload.bz = f.bz.trim()
return payload
},
// ==================== 删除 ====================
handleDelete(row) {
this.$confirm(`确定要删除「${row.mc || row.bh || '该记录'}」吗?`, '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => deleteJointTraining(row.bh)).then(() => {
this.$message.success('删除成功')
this.fetchList()
}).catch(() => {})
},
// ==================== 详情 ====================
handleDetail(row) {
getJointTraining(row.bh).then(res => {
this.detailData = (res && res.data) || {}
this.detailVisible = true
}).catch(() => {})
},
// ==================== 导出 ====================
handleExport() {
exportJointTraining().then(blob => {
saveAs(blob, '联教联训管理.xlsx')
this.$message.success('导出成功')
}).catch(() => {})
},
// ==================== 文件选择与导入 ====================
handleChooseFile() {
this.$refs.fileInputRef && this.$refs.fileInputRef.click()
},
@@ -275,48 +410,31 @@ export default {
this.selectedFile = (input.files && input.files[0]) || null
},
/** 下载模板(生成 CSV 模板文件,字段对齐后端接口) */
handleDownloadTemplate() {
const csvContent = '\uFEFF' + '任务编号,名称,教员编号,任务类别,课时量,合班类型,是否评优,年度,学期,说明\n'
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' })
const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = '联教联训数据文件模板.csv'
link.click()
URL.revokeObjectURL(link.href)
this.$message.success('已下载联教联训数据文件模板')
},
// TODO: 后端接口未提供,上传为前端模拟;接口就绪后替换为 /log/joint-training/import-excel
handleUpload() {
if (!this.selectedFile) {
this.$message.warning('请先选择文件')
return
}
this.$message.success(`文件「${this.selectedFile.name}」上传成功(前端模拟)`)
this.selectedFile = null
this.handleQuery()
this.uploading = true
importJointTraining(this.selectedFile).then(res => {
const count = res && res.data
if (count !== null && count !== undefined) {
this.$message.success(`导入成功,共 ${count} 条记录`)
} else {
this.$message.success((res && res.msg) || '导入成功')
}
this.selectedFile = null
this.$refs.fileInputRef && (this.$refs.fileInputRef.value = '')
this.pageNum = 1
this.fetchList()
}).catch(() => {}).finally(() => {
this.uploading = false
})
},
// ==================== 4. 添加 ====================
// TODO: 后端接口未提供,提交为前端模拟;接口就绪后替换为 addJointTraining
handleAdd() {
this.$refs.formRef.validate((valid) => {
if (!valid) {
this.$message.warning('请完善必填项后再提交')
return
}
this.addLoading = true
setTimeout(() => {
this.$message.success('添加成功(前端模拟)')
Object.assign(this.form, {
bh: this.genBh(), mc: '', jybh: '', rwlb: '', ksl: '',
hflx: 0, sfpy: 0, nd: '2026', xq: '2026-2027-1', sm: ''
})
this.handleQuery()
this.addLoading = false
}, 300)
})
handleDownloadTemplate() {
// 后端暂未提供模板下载接口,仅作提示
this.$message.info('后端暂未提供该接口')
}
}
}
@@ -347,30 +465,19 @@ export default {
.search-actions {
display: flex;
justify-content: flex-end;
margin-top: 8px;
padding-top: 12px;
border-top: 1px solid #ebeef5;
align-items: center;
height: 40px;
}
}
.form-title {
font-size: 14px;
font-weight: 600;
color: #303133;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid #ebeef5;
}
}
// ==================== 2. 文件操作区域 ====================
// ==================== 2. 操作区域 ====================
.upload-card {
margin-bottom: 16px;
.upload-row {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 12px;
@@ -390,12 +497,6 @@ export default {
}
}
}
.upload-tip {
margin-top: 10px;
font-size: 12px;
color: #f56c6c;
}
}
// ==================== 3. 数据表格 ====================
@@ -414,6 +515,17 @@ export default {
justify-content: flex-end;
margin-top: 12px;
}
.text-danger {
color: #f56c6c;
}
}
// ==================== 4. 新增/修改表单 ====================
.add-form {
max-height: 62vh;
overflow-y: auto;
padding-right: 4px;
}
}
</style>
+670 -6
View File
@@ -1,15 +1,679 @@
<template>
<div class="app-container">
<placeholder-page title="方案管理" icon="documentation"
description="用于维护课时的各类方案信息,如教学方案的配置、启用与调整,支持查询与维护。" />
<div class="app-container plan-page">
<!-- ==================== Tab 切换 ==================== -->
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
<el-tab-pane label="课时费方案" name="fee" />
<el-tab-pane label="课时方案" name="coeff" />
</el-tabs>
<!-- ==================== 1. 查询与新增 ==================== -->
<el-card shadow="never" class="search-card">
<el-form :inline="true" class="search-form" @submit.native.prevent>
<el-form-item label="名称">
<el-input v-model="currentForm.mc" placeholder="请输入名称" clearable style="width: 220px" @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">查询</el-button>
<el-button type="primary" plain @click="handleAdd">新增{{ currentTitle }}</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- ==================== 2. 数据表格 ==================== -->
<el-card shadow="never" class="table-card">
<!-- 课时费方案 -->
<el-table v-if="activeTab === 'fee'" v-loading="feeState.loading" :data="feeState.list" border stripe style="width: 100%">
<el-table-column type="index" label="序号" width="55" align="center" />
<el-table-column prop="bh" label="编号" width="150" align="center" show-overflow-tooltip />
<el-table-column prop="mc" label="名称" min-width="130" show-overflow-tooltip />
<el-table-column prop="mrbzksl" label="默认标准课时量" width="120" align="right">
<template slot-scope="{ row }">{{ fmtValue(row.mrbzksl) }}</template>
</el-table-column>
<el-table-column prop="mrksfbz" label="默认课时费标准" width="120" align="right">
<template slot-scope="{ row }">{{ fmtValue(row.mrksfbz) }}</template>
</el-table-column>
<el-table-column prop="mrclksfbz" label="默认超量课时费标准" width="150" align="right">
<template slot-scope="{ row }">{{ fmtValue(row.mrclksfbz) }}</template>
</el-table-column>
<el-table-column prop="xgjxbzksfbz" label="相关教学补助课时费标准" width="180" align="right">
<template slot-scope="{ row }">{{ fmtValue(row.xgjxbzksfbz) }}</template>
</el-table-column>
<el-table-column prop="jxyxbz" label="绩效优秀补助" width="110" align="right">
<template slot-scope="{ row }">{{ fmtValue(row.jxyxbz) }}</template>
</el-table-column>
<el-table-column prop="jxlhbz" label="绩效良好补助" width="110" align="right">
<template slot-scope="{ row }">{{ fmtValue(row.jxlhbz) }}</template>
</el-table-column>
<el-table-column prop="jxzdbz" label="绩效中等补助" width="110" align="right">
<template slot-scope="{ row }">{{ fmtValue(row.jxzdbz) }}</template>
</el-table-column>
<el-table-column prop="jxjgbz" label="绩效及格补助" width="110" align="right">
<template slot-scope="{ row }">{{ fmtValue(row.jxjgbz) }}</template>
</el-table-column>
<el-table-column prop="jxbjgbz" label="绩效不及格补助" width="120" align="right">
<template slot-scope="{ row }">{{ fmtValue(row.jxbjgbz) }}</template>
</el-table-column>
<el-table-column prop="sm" label="说明" min-width="120" show-overflow-tooltip />
<el-table-column label="操作" width="160" align="center" fixed="right">
<template slot-scope="{ row }">
<el-button type="text" size="small" @click="handleDetail(row)">详情</el-button>
<el-button type="text" size="small" @click="handleEdit(row)">修改</el-button>
<el-button type="text" size="small" class="text-danger" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 课时方案 -->
<el-table v-else v-loading="coeffState.loading" :data="coeffState.list" border stripe style="width: 100%">
<el-table-column type="index" label="序号" width="55" align="center" />
<el-table-column prop="bh" label="编号" width="150" align="center" show-overflow-tooltip />
<el-table-column prop="mc" label="名称" min-width="130" show-overflow-tooltip />
<el-table-column prop="zjksxs" label="主讲课时系数" width="110" align="right">
<template slot-scope="{ row }">{{ fmtValue(row.zjksxs) }}</template>
</el-table-column>
<el-table-column prop="fjksxs" label="辅讲课时系数" width="110" align="right">
<template slot-scope="{ row }">{{ fmtValue(row.fjksxs) }}</template>
</el-table-column>
<el-table-column prop="mbxyrs" label="满班学员人数" width="110" align="right">
<template slot-scope="{ row }">{{ fmtValue(row.mbxyrs) }}</template>
</el-table-column>
<el-table-column prop="dz10xybjxs" label="递增10学员班级系数" width="150" align="right">
<template slot-scope="{ row }">{{ fmtValue(row.dz10xybjxs) }}</template>
</el-table-column>
<el-table-column prop="bjxssx" label="班级系数上限" width="110" align="right">
<template slot-scope="{ row }">{{ fmtValue(row.bjxssx) }}</template>
</el-table-column>
<el-table-column prop="d78jksl" label="第78节课时量" width="110" align="right">
<template slot-scope="{ row }">{{ fmtValue(row.d78jksl) }}</template>
</el-table-column>
<el-table-column prop="yjksl" label="夜间课时量" width="100" align="right">
<template slot-scope="{ row }">{{ fmtValue(row.yjksl) }}</template>
</el-table-column>
<el-table-column prop="mrktjxffxs" label="默认课堂教法系数" width="130" align="right">
<template slot-scope="{ row }">{{ fmtValue(row.mrktjxffxs) }}</template>
</el-table-column>
<el-table-column prop="hbxszl" label="合班系数增量" width="110" align="right">
<template slot-scope="{ row }">{{ fmtValue(row.hbxszl) }}</template>
</el-table-column>
<el-table-column prop="hbxssx" label="合班系数上限" width="110" align="right">
<template slot-scope="{ row }">{{ fmtValue(row.hbxssx) }}</template>
</el-table-column>
<el-table-column prop="sm" label="说明" min-width="120" show-overflow-tooltip />
<el-table-column label="操作" width="120" align="center" fixed="right">
<template slot-scope="{ row }">
<el-button type="text" size="small" @click="handleDetail(row)">详情</el-button>
<el-button type="text" size="small" @click="handleEdit(row)">修改</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
:current-page="currentState.pageNum"
:page-size="currentState.pageSize"
:total="currentState.total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
class="pagination"
@current-change="handlePageChange"
@size-change="handleSizeChange"
/>
</el-card>
<!-- ==================== 3. 新增/修改对话框 ==================== -->
<el-dialog
:visible="dialogVisible"
:title="dialogTitle"
width="760px"
:close-on-click-modal="false"
@update:visible="val => dialogVisible = val"
>
<!-- 课时费方案表单 -->
<el-form v-if="activeTab === 'fee'" ref="feeFormRef" :model="feeForm" :rules="feeRules" label-width="160px" class="add-form">
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="编号">
<el-input v-model="feeForm.bh" placeholder="留空则自动生成" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="名称" prop="mc">
<el-input v-model="feeForm.mc" placeholder="请输入名称" clearable />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="默认标准课时量" prop="mrbzksl">
<el-input-number v-model="feeForm.mrbzksl" :min="0" :precision="2" :step="1" class="w-full" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="默认课时费标准" prop="mrksfbz">
<el-input-number v-model="feeForm.mrksfbz" :min="0" :precision="2" :step="1" class="w-full" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="默认超量课时费标准" prop="mrclksfbz">
<el-input-number v-model="feeForm.mrclksfbz" :min="0" :precision="2" :step="1" class="w-full" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="相关教学补助课时费标准" prop="xgjxbzksfbz">
<el-input-number v-model="feeForm.xgjxbzksfbz" :min="0" :precision="2" :step="1" class="w-full" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="绩效优秀补助" prop="jxyxbz">
<el-input-number v-model="feeForm.jxyxbz" :min="0" :precision="2" :step="1" class="w-full" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="绩效良好补助" prop="jxlhbz">
<el-input-number v-model="feeForm.jxlhbz" :min="0" :precision="2" :step="1" class="w-full" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="绩效中等补助" prop="jxzdbz">
<el-input-number v-model="feeForm.jxzdbz" :min="0" :precision="2" :step="1" class="w-full" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="绩效及格补助" prop="jxjgbz">
<el-input-number v-model="feeForm.jxjgbz" :min="0" :precision="2" :step="1" class="w-full" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="绩效不及格补助" prop="jxbjgbz">
<el-input-number v-model="feeForm.jxbjgbz" :min="0" :precision="2" :step="1" class="w-full" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="说明">
<el-input v-model="feeForm.sm" type="textarea" :rows="2" placeholder="请输入说明" />
</el-form-item>
<el-form-item label="备注">
<el-input v-model="feeForm.bz" type="textarea" :rows="2" placeholder="请输入备注" />
</el-form-item>
</el-form>
<!-- 课时方案表单 -->
<el-form v-else ref="coeffFormRef" :model="coeffForm" :rules="coeffRules" label-width="160px" class="add-form">
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="编号">
<el-input v-model="coeffForm.bh" placeholder="留空则自动生成" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="名称" prop="mc">
<el-input v-model="coeffForm.mc" placeholder="请输入名称" clearable />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="主讲课时系数">
<el-input-number v-model="coeffForm.zjksxs" :min="0" :precision="2" :step="0.1" class="w-full" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="辅讲课时系数">
<el-input-number v-model="coeffForm.fjksxs" :min="0" :precision="2" :step="0.1" class="w-full" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="满班学员人数">
<el-input-number v-model="coeffForm.mbxyrs" :min="0" :precision="0" :step="1" class="w-full" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="递增10学员班级系数">
<el-input-number v-model="coeffForm.dz10xybjxs" :min="0" :precision="2" :step="0.1" class="w-full" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="班级系数上限">
<el-input-number v-model="coeffForm.bjxssx" :min="0" :precision="2" :step="0.1" class="w-full" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="第78节课时量">
<el-input-number v-model="coeffForm.d78jksl" :min="0" :precision="2" :step="1" class="w-full" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="夜间课时量">
<el-input-number v-model="coeffForm.yjksl" :min="0" :precision="2" :step="1" class="w-full" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="默认课堂教学方法系数">
<el-input-number v-model="coeffForm.mrktjxffxs" :min="0" :precision="2" :step="0.1" class="w-full" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="合班系数增量">
<el-input-number v-model="coeffForm.hbxszl" :min="0" :precision="2" :step="0.1" class="w-full" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="合班系数上限">
<el-input-number v-model="coeffForm.hbxssx" :min="0" :precision="2" :step="0.1" class="w-full" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="说明">
<el-input v-model="coeffForm.sm" type="textarea" :rows="2" placeholder="请输入说明" />
</el-form-item>
<el-form-item label="备注">
<el-input v-model="coeffForm.bz" type="textarea" :rows="2" placeholder="请输入备注" />
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" :loading="saving" @click="handleSubmit">确定</el-button>
</div>
</el-dialog>
<!-- ==================== 4. 详情对话框 ==================== -->
<el-dialog :visible="detailVisible" :title="`${currentTitle}详情`" width="680px" @update:visible="val => detailVisible = val">
<el-descriptions v-if="activeTab === 'fee'" :column="2" border>
<el-descriptions-item label="编号">{{ detailData.bh }}</el-descriptions-item>
<el-descriptions-item label="名称">{{ detailData.mc }}</el-descriptions-item>
<el-descriptions-item label="默认标准课时量">{{ fmtValue(detailData.mrbzksl) }}</el-descriptions-item>
<el-descriptions-item label="默认课时费标准">{{ fmtValue(detailData.mrksfbz) }}</el-descriptions-item>
<el-descriptions-item label="默认超量课时费标准">{{ fmtValue(detailData.mrclksfbz) }}</el-descriptions-item>
<el-descriptions-item label="相关教学补助课时费标准">{{ fmtValue(detailData.xgjxbzksfbz) }}</el-descriptions-item>
<el-descriptions-item label="绩效优秀补助">{{ fmtValue(detailData.jxyxbz) }}</el-descriptions-item>
<el-descriptions-item label="绩效良好补助">{{ fmtValue(detailData.jxlhbz) }}</el-descriptions-item>
<el-descriptions-item label="绩效中等补助">{{ fmtValue(detailData.jxzdbz) }}</el-descriptions-item>
<el-descriptions-item label="绩效及格补助">{{ fmtValue(detailData.jxjgbz) }}</el-descriptions-item>
<el-descriptions-item label="绩效不及格补助">{{ fmtValue(detailData.jxbjgbz) }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ fmtEmpty(detailData.cjsj) }}</el-descriptions-item>
<el-descriptions-item label="说明" :span="2">{{ fmtEmpty(detailData.sm) }}</el-descriptions-item>
<el-descriptions-item label="备注" :span="2">{{ fmtEmpty(detailData.bz) }}</el-descriptions-item>
</el-descriptions>
<el-descriptions v-else :column="2" border>
<el-descriptions-item label="编号">{{ detailData.bh }}</el-descriptions-item>
<el-descriptions-item label="名称">{{ detailData.mc }}</el-descriptions-item>
<el-descriptions-item label="主讲课时系数">{{ fmtValue(detailData.zjksxs) }}</el-descriptions-item>
<el-descriptions-item label="辅讲课时系数">{{ fmtValue(detailData.fjksxs) }}</el-descriptions-item>
<el-descriptions-item label="满班学员人数">{{ fmtValue(detailData.mbxyrs) }}</el-descriptions-item>
<el-descriptions-item label="递增10学员班级系数">{{ fmtValue(detailData.dz10xybjxs) }}</el-descriptions-item>
<el-descriptions-item label="班级系数上限">{{ fmtValue(detailData.bjxssx) }}</el-descriptions-item>
<el-descriptions-item label="第78节课时量">{{ fmtValue(detailData.d78jksl) }}</el-descriptions-item>
<el-descriptions-item label="夜间课时量">{{ fmtValue(detailData.yjksl) }}</el-descriptions-item>
<el-descriptions-item label="默认课堂教学方法系数">{{ fmtValue(detailData.mrktjxffxs) }}</el-descriptions-item>
<el-descriptions-item label="合班系数增量">{{ fmtValue(detailData.hbxszl) }}</el-descriptions-item>
<el-descriptions-item label="合班系数上限">{{ fmtValue(detailData.hbxssx) }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ fmtEmpty(detailData.cjsj) }}</el-descriptions-item>
<el-descriptions-item label="说明" :span="2">{{ fmtEmpty(detailData.sm) }}</el-descriptions-item>
<el-descriptions-item label="备注" :span="2">{{ fmtEmpty(detailData.bz) }}</el-descriptions-item>
</el-descriptions>
<div slot="footer">
<el-button @click="detailVisible = false">关闭</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import PlaceholderPage from "@/components/PlaceholderPage"
import {
listFeeStandard,
getFeeStandard,
addFeeStandard,
updateFeeStandard,
deleteFeeStandard,
listCoefficientPlan,
getCoefficientPlan,
addCoefficientPlan,
updateCoefficientPlan
} from '@/api/classHour/plan'
export default {
name: "ClassHourPlan",
components: { PlaceholderPage }
name: 'ClassHourPlan',
data() {
return {
// ==================== Tab ====================
activeTab: 'fee',
// ==================== 查询条件 ====================
feeSearch: { mc: '' },
coeffSearch: { mc: '' },
// ==================== 列表状态 ====================
feeState: this.createEmptyState(),
coeffState: this.createEmptyState(),
// ==================== 新增/修改 ====================
dialogVisible: false,
dialogTitle: '',
saving: false,
feeForm: this.createEmptyFeeForm(),
coeffForm: this.createEmptyCoeffForm(),
feeRules: {
mc: [{ required: true, message: '请输入名称', trigger: 'blur' }],
mrbzksl: [{ required: true, message: '请输入默认标准课时量', trigger: 'blur' }],
mrksfbz: [{ required: true, message: '请输入默认课时费标准', trigger: 'blur' }],
mrclksfbz: [{ required: true, message: '请输入默认超量课时费标准', trigger: 'blur' }],
xgjxbzksfbz: [{ required: true, message: '请输入相关教学补助课时费标准', trigger: 'blur' }],
jxyxbz: [{ required: true, message: '请输入绩效优秀补助', trigger: 'blur' }],
jxlhbz: [{ required: true, message: '请输入绩效良好补助', trigger: 'blur' }],
jxzdbz: [{ required: true, message: '请输入绩效中等补助', trigger: 'blur' }],
jxjgbz: [{ required: true, message: '请输入绩效及格补助', trigger: 'blur' }],
jxbjgbz: [{ required: true, message: '请输入绩效不及格补助', trigger: 'blur' }]
},
coeffRules: {
mc: [{ required: true, message: '请输入名称', trigger: 'blur' }]
},
// ==================== 详情 ====================
detailVisible: false,
detailData: {}
}
},
computed: {
currentForm() {
return this.activeTab === 'fee' ? this.feeSearch : this.coeffSearch
},
currentState() {
return this.activeTab === 'fee' ? this.feeState : this.coeffState
},
currentTitle() {
return this.activeTab === 'fee' ? '课时费方案' : '课时方案'
}
},
mounted() {
this.fetchList()
},
methods: {
createEmptyState() {
return { list: [], loading: false, pageNum: 1, pageSize: 10, total: 0 }
},
createEmptyFeeForm() {
return {
bh: '',
mc: '',
mrbzksl: null,
mrksfbz: null,
mrclksfbz: null,
xgjxbzksfbz: null,
jxyxbz: null,
jxlhbz: null,
jxzdbz: null,
jxjgbz: null,
jxbjgbz: null,
sm: '',
bz: ''
}
},
createEmptyCoeffForm() {
return {
bh: '',
mc: '',
zjksxs: null,
fjksxs: null,
mbxyrs: null,
dz10xybjxs: null,
bjxssx: null,
d78jksl: null,
yjksl: null,
mrktjxffxs: null,
hbxszl: null,
hbxssx: null,
sm: '',
bz: ''
}
},
// ==================== 查询 ====================
fetchList() {
const state = this.currentState
const params = { pageNum: state.pageNum, pageSize: state.pageSize }
if (this.currentForm.mc && this.currentForm.mc.trim()) params.mc = this.currentForm.mc.trim()
state.loading = true
const req = this.activeTab === 'fee' ? listFeeStandard(params) : listCoefficientPlan(params)
req.then(res => {
const data = (res && res.data) || {}
state.list = data.records || []
state.total = data.total || 0
}).catch(() => {
state.list = []
state.total = 0
}).finally(() => {
state.loading = false
})
},
handleTabClick() {
this.currentState.pageNum = 1
this.fetchList()
},
handleQuery() {
this.currentState.pageNum = 1
this.fetchList()
},
handlePageChange(page) {
this.currentState.pageNum = page
this.fetchList()
},
handleSizeChange(size) {
this.currentState.pageSize = size
this.currentState.pageNum = 1
this.fetchList()
},
// ==================== 新增 / 修改 ====================
handleAdd() {
this.feeForm = this.createEmptyFeeForm()
this.coeffForm = this.createEmptyCoeffForm()
this.dialogTitle = `新增${this.currentTitle}`
this.dialogVisible = true
},
handleEdit(row) {
const req = this.activeTab === 'fee' ? getFeeStandard(row.bh) : getCoefficientPlan(row.bh)
req.then(res => {
const d = (res && res.data) || {}
if (this.activeTab === 'fee') {
this.feeForm = {
bh: d.bh || '',
mc: d.mc || '',
mrbzksl: this.toNull(d.mrbzksl),
mrksfbz: this.toNull(d.mrksfbz),
mrclksfbz: this.toNull(d.mrclksfbz),
xgjxbzksfbz: this.toNull(d.xgjxbzksfbz),
jxyxbz: this.toNull(d.jxyxbz),
jxlhbz: this.toNull(d.jxlhbz),
jxzdbz: this.toNull(d.jxzdbz),
jxjgbz: this.toNull(d.jxjgbz),
jxbjgbz: this.toNull(d.jxbjgbz),
sm: d.sm || '',
bz: d.bz || ''
}
} else {
this.coeffForm = {
bh: d.bh || '',
mc: d.mc || '',
zjksxs: this.toNull(d.zjksxs),
fjksxs: this.toNull(d.fjksxs),
mbxyrs: this.toNull(d.mbxyrs),
dz10xybjxs: this.toNull(d.dz10xybjxs),
bjxssx: this.toNull(d.bjxssx),
d78jksl: this.toNull(d.d78jksl),
yjksl: this.toNull(d.yjksl),
mrktjxffxs: this.toNull(d.mrktjxffxs),
hbxszl: this.toNull(d.hbxszl),
hbxssx: this.toNull(d.hbxssx),
sm: d.sm || '',
bz: d.bz || ''
}
}
this.dialogTitle = `修改${this.currentTitle}`
this.dialogVisible = true
}).catch(() => {})
},
handleSubmit() {
const isFee = this.activeTab === 'fee'
const formRef = isFee ? this.$refs.feeFormRef : this.$refs.coeffFormRef
formRef.validate(valid => {
if (!valid) {
this.$message.warning('请完善必填项后再提交')
return
}
const payload = isFee ? this.buildFeePayload() : this.buildCoeffPayload()
this.saving = true
const req = payload.bh
? (isFee ? updateFeeStandard(payload) : updateCoefficientPlan(payload))
: (isFee ? addFeeStandard(payload) : addCoefficientPlan(payload))
req.then(() => {
this.$message.success(this.dialogTitle.indexOf('修改') === 0 ? '修改成功' : '新增成功')
this.dialogVisible = false
this.fetchList()
}).catch(() => {}).finally(() => {
this.saving = false
})
})
},
toNull(val) {
return val === null || val === undefined ? null : val
},
/** 课时费方案请求体:仅含实体非空字段(创建/修改时间后端自动维护) */
buildFeePayload() {
const f = this.feeForm
const p = {}
if (f.bh && f.bh.trim()) p.bh = f.bh.trim()
if (f.mc && f.mc.trim()) p.mc = f.mc.trim()
;['mrbzksl', 'mrksfbz', 'mrclksfbz', 'xgjxbzksfbz', 'jxyxbz', 'jxlhbz', 'jxzdbz', 'jxjgbz', 'jxbjgbz'].forEach(k => {
if (f[k] !== null && f[k] !== undefined) p[k] = f[k]
})
if (f.sm && f.sm.trim()) p.sm = f.sm.trim()
if (f.bz && f.bz.trim()) p.bz = f.bz.trim()
return p
},
/** 课时方案请求体:仅含实体非空字段 */
buildCoeffPayload() {
const f = this.coeffForm
const p = {}
if (f.bh && f.bh.trim()) p.bh = f.bh.trim()
if (f.mc && f.mc.trim()) p.mc = f.mc.trim()
;['zjksxs', 'fjksxs', 'mbxyrs', 'dz10xybjxs', 'bjxssx', 'd78jksl', 'yjksl', 'mrktjxffxs', 'hbxszl', 'hbxssx'].forEach(k => {
if (f[k] !== null && f[k] !== undefined) p[k] = f[k]
})
if (f.sm && f.sm.trim()) p.sm = f.sm.trim()
if (f.bz && f.bz.trim()) p.bz = f.bz.trim()
return p
},
// ==================== 删除(仅课时费方案有删除接口) ====================
handleDelete(row) {
this.$confirm(`确定要删除「${row.mc || row.bh || '该方案'}」吗?`, '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => deleteFeeStandard(row.bh)).then(() => {
this.$message.success('删除成功')
this.fetchList()
}).catch(() => {})
},
// ==================== 详情 ====================
handleDetail(row) {
const req = this.activeTab === 'fee' ? getFeeStandard(row.bh) : getCoefficientPlan(row.bh)
req.then(res => {
this.detailData = (res && res.data) || {}
this.detailVisible = true
}).catch(() => {})
},
/** 数值空值显示为 '-'(保留 0) */
fmtValue(val) {
return val === null || val === undefined ? '-' : val
},
/** 空值显示为 '-' */
fmtEmpty(val) {
return val === null || val === undefined || val === '' ? '-' : val
}
}
}
</script>
<style scoped lang="scss">
.app-container {
padding: 20px;
}
.plan-page {
.list-title {
font-size: 16px;
font-weight: 600;
color: #303133;
margin-bottom: 12px;
}
// ==================== 1. 查询区域 ====================
.search-card {
margin-bottom: 16px;
.search-form {
.el-form-item {
margin-bottom: 0;
}
}
}
// ==================== 2. 数据表格 ====================
.table-card {
.pagination {
display: flex;
justify-content: flex-end;
margin-top: 12px;
}
.text-danger {
color: #f56c6c;
}
}
// ==================== 3. 新增/修改表单 ====================
.add-form {
max-height: 62vh;
overflow-y: auto;
padding-right: 4px;
.w-full {
width: 100%;
}
}
}
</style>
+213 -212
View File
@@ -1,223 +1,250 @@
<template>
<div class="app-container subsidy-page">
<div class="list-title">课时补助核算</div>
<div class="top-tip">{{ tipText }}</div>
<!-- ==================== 1. 新增核算区域 ==================== -->
<el-card shadow="never" class="search-card">
<div class="form-title">新增</div>
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px" class="search-form">
<el-row :gutter="0">
<!-- 左栏 -->
<el-col :span="12">
<el-form-item label="学期名称" prop="semesterName">
<el-input v-model="form.semesterName" placeholder="请输入学期名称" clearable />
</el-form-item>
<el-form-item label="开始日期" prop="startDate">
<el-input v-model="form.startDate" placeholder="如:2016-03-01" clearable />
</el-form-item>
<el-form-item label="课时补助标准">
<el-select v-model="form.subsidyStandard" placeholder="请选择" class="w-full">
<el-option v-for="item in subsidyStandardOptions" :key="item" :label="item" :value="item" />
</el-select>
<!-- ==================== Tab 切换 ==================== -->
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
<el-tab-pane label="课时统计" name="hour" />
<el-tab-pane label="课时费统计" name="fee" />
</el-tabs>
<!-- ==================== 1. 查询条件 ==================== -->
<!-- 课时统计 -->
<el-card v-if="activeTab === 'hour'" shadow="never" class="search-card">
<el-form :model="hourForm" label-width="100px" class="search-form" @submit.native.prevent>
<el-row :gutter="24">
<el-col :xs="24" :sm="12" :md="6">
<el-form-item label="学期年度">
<el-input v-model="hourForm.nd" placeholder="如 2026" clearable />
</el-form-item>
</el-col>
<!-- 右栏 -->
<el-col :span="12">
<el-form-item label="任务类别">
<el-radio-group v-model="form.taskCategory">
<el-radio v-for="item in taskCategoryOptions" :key="item.value" :label="item.value">{{ item.label }}</el-radio>
</el-radio-group>
<el-col :xs="24" :sm="12" :md="6">
<el-form-item label="学期序号">
<el-input v-model="hourForm.xq" placeholder="如 1" clearable />
</el-form-item>
<el-form-item label="结束日期" prop="endDate">
<el-input v-model="form.endDate" placeholder="如:2016-08-31" clearable />
</el-form-item>
<el-form-item label="课时核算标准">
<el-select v-model="form.accountStandard" placeholder="请选择" class="w-full">
<el-option v-for="item in accountStandardOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<el-form-item label="教员姓名">
<el-input v-model="hourForm.jyxm" placeholder="请输入教员姓名" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="6" class="search-actions-col">
<div class="search-actions">
<el-button type="primary" @click="handleQuery">查询</el-button>
<el-button type="primary" plain @click="handleExport">导出 Excel</el-button>
</div>
</el-col>
</el-row>
</el-form>
</el-card>
<!-- 课时费统计 -->
<el-card v-else shadow="never" class="search-card">
<el-form :model="feeForm" label-width="100px" class="search-form" @submit.native.prevent>
<el-row :gutter="24">
<el-col :xs="24" :sm="12" :md="6">
<el-form-item label="学期年度">
<el-input v-model="feeForm.nd" placeholder="如 2026" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<el-form-item label="学期序号">
<el-input v-model="feeForm.xq" placeholder="如 1" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<el-form-item label="教员姓名">
<el-input v-model="feeForm.jyxm" placeholder="请输入教员姓名" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<el-form-item label="标准编号">
<el-input v-model="feeForm.ksfbzbh" placeholder="课时费标准编号" clearable />
</el-form-item>
</el-col>
<el-col :span="24" class="search-actions-col">
<div class="search-actions">
<el-button type="primary" @click="handleQuery">查询</el-button>
<el-button type="primary" plain @click="handleExport">导出 Excel</el-button>
</div>
</el-col>
</el-row>
<div class="search-actions">
<el-button type="primary" :loading="addLoading" @click="handleAdd">添加</el-button>
</div>
</el-form>
</el-card>
<!-- ==================== 2. 数据表格 ==================== -->
<el-card shadow="never" class="table-card">
<el-table v-loading="queryLoading" :data="tableData" border stripe style="width: 100%">
<el-table-column label="详细" width="80" fixed align="center">
<template slot-scope="{ row }">
<el-button type="text" @click="handleDetail(row)">详细</el-button>
</template>
<div class="table-title">{{ activeTab === 'hour' ? '课时统计列表' : '课时费统计列表' }}</div>
<el-table v-loading="currentState.loading" :data="currentState.list" border stripe style="width: 100%">
<el-table-column type="index" label="序号" width="55" align="center" />
<el-table-column prop="jysdh" label="教研室代号" width="100" align="center" show-overflow-tooltip />
<el-table-column prop="jysmc" label="教研室名称" width="120" show-overflow-tooltip />
<el-table-column prop="jybh" label="教员编号" width="110" align="center" show-overflow-tooltip />
<el-table-column prop="jyxm" label="教员姓名" width="100" align="center" show-overflow-tooltip />
<el-table-column prop="zc" label="职称" width="90" align="center" show-overflow-tooltip />
<el-table-column prop="nf" label="年份" width="70" align="center" />
<el-table-column prop="sbn" label="上半年" width="90" align="right" header-align="center">
<template slot-scope="{ row }">{{ fmtValue(row.sbn) }}</template>
</el-table-column>
<el-table-column prop="semesterName" label="学期名称" width="120" />
<el-table-column prop="taskCategory" label="任务类别" width="180" />
<el-table-column prop="startDate" label="核算开始日期" width="120" />
<el-table-column prop="endDate" label="核算结束日期" width="120" />
<el-table-column prop="totalHours" label="总课时量" width="100" align="right" />
<el-table-column prop="qualityHours" label="优质课时量" width="100" align="right" />
<el-table-column prop="qualityRatio" label="优质课时量比例" width="120" align="right" />
<el-table-column prop="totalSubsidy" label="总补助金额" width="120" align="right" />
<el-table-column prop="accountStandard" label="课时核算标准" show-overflow-tooltip />
<el-table-column prop="subsidyStandard" label="课时补助标准" width="180" show-overflow-tooltip />
<el-table-column prop="status" label="状态" width="100" align="center">
<template slot-scope="{ row }">
<span :class="row.status === '已核算' ? 'status-done' : 'status-doing'">
{{ row.status }}
</span>
</template>
<el-table-column prop="xbn" label="下半年" width="90" align="right" header-align="center">
<template slot-scope="{ row }">{{ fmtValue(row.xbn) }}</template>
</el-table-column>
<el-table-column prop="zj" label="总计" width="90" align="right" header-align="center">
<template slot-scope="{ row }">{{ fmtValue(row.zj) }}</template>
</el-table-column>
<el-table-column prop="bzksl" label="标准课时量" width="100" align="right" header-align="center">
<template slot-scope="{ row }">{{ fmtValue(row.bzksl) }}</template>
</el-table-column>
<el-table-column prop="cks" label="超课时" width="90" align="right" header-align="center">
<template slot-scope="{ row }">{{ fmtValue(row.cks) }}</template>
</el-table-column>
<el-table-column prop="jsgs" label="计算过程" min-width="140" show-overflow-tooltip />
<el-table-column prop="jg" label="结果" width="90" align="right" header-align="center">
<template slot-scope="{ row }">{{ fmtValue(row.jg) }}</template>
</el-table-column>
<el-table-column prop="ksfbzmc" label="课时费标准" width="110" align="center" show-overflow-tooltip />
<el-table-column prop="ksfbz" label="课时费单价" width="100" align="right" header-align="center">
<template slot-scope="{ row }">{{ fmtValue(row.ksfbz) }}</template>
</el-table-column>
<el-table-column prop="clksfbz" label="超量课时费单价" width="110" align="right" header-align="center">
<template slot-scope="{ row }">{{ fmtValue(row.clksfbz) }}</template>
</el-table-column>
</el-table>
<div class="table-footer">
<span>总记录数:{{ tableData.length }} 条</span>
<span>总课时量:{{ totalHours }}</span>
<span>优质课时量:{{ totalQualityHours }}</span>
<span>总补助金额:{{ totalSubsidy }} 元</span>
</div>
<el-pagination
:current-page="currentState.pageNum"
:page-size="currentState.pageSize"
:total="currentState.total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
class="pagination"
@current-change="handlePageChange"
@size-change="handleSizeChange"
/>
</el-card>
<!-- ==================== 3. 详情对话框 ==================== -->
<el-dialog
:visible="detailVisible"
title="课时补助核算详情"
width="560px"
:close-on-click-modal="false"
@update:visible="val => detailVisible = val"
>
<el-descriptions v-if="detailRow" :column="1" border>
<el-descriptions-item label="学期名称">{{ detailRow.semesterName }}</el-descriptions-item>
<el-descriptions-item label="任务类别">{{ detailRow.taskCategory }}</el-descriptions-item>
<el-descriptions-item label="核算开始日期">{{ detailRow.startDate }}</el-descriptions-item>
<el-descriptions-item label="核算结束日期">{{ detailRow.endDate }}</el-descriptions-item>
<el-descriptions-item label="总课时量">{{ detailRow.totalHours }}</el-descriptions-item>
<el-descriptions-item label="优质课时量">{{ detailRow.qualityHours }}</el-descriptions-item>
<el-descriptions-item label="优质课时量比例">{{ detailRow.qualityRatio }}</el-descriptions-item>
<el-descriptions-item label="总补助金额">{{ detailRow.totalSubsidy }} 元</el-descriptions-item>
<el-descriptions-item label="课时核算标准">{{ detailRow.accountStandard }}</el-descriptions-item>
<el-descriptions-item label="课时补助标准">{{ detailRow.subsidyStandard }}</el-descriptions-item>
<el-descriptions-item label="状态">{{ detailRow.status }}</el-descriptions-item>
</el-descriptions>
<div slot="footer">
<el-button @click="detailVisible = false">关闭</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
// 模拟核算列表数据(后端接口未提供,接口就绪后可删除并改为接口加载)
const mockSummaryData = [
{ bh: 'H001', mc: '2022年秋季学期课时补助核算', rwlb: '全军院校训练规划内任务', hsksrq: '2022-09-01T00:00:00', hsjsrq: '2023-01-15T00:00:00', zksl: 1200, yzksl: 800, yzkslbl: 0.6667, zbzje: 24000, zt: '已核算' },
{ bh: 'H002', mc: '2022年春季学期课时补助核算', rwlb: '其他任务(含研究生导师指导)', hsksrq: '2022-03-01T00:00:00', hsjsrq: '2022-08-31T00:00:00', zksl: 900, yzksl: 540, yzkslbl: 0.6, zbzje: 18000, zt: '核算中' },
{ bh: 'H003', mc: '2021年秋季学期课时补助核算', rwlb: '全军院校训练规划内任务', hsksrq: '2021-09-01T00:00:00', hsjsrq: '2022-01-15T00:00:00', zksl: 1100, yzksl: 770, yzkslbl: 0.7, zbzje: 22000, zt: '已核算' }
]
import { saveAs } from 'file-saver'
import {
listHourStatistics,
exportHourStatistics,
listFeeStatistics,
exportFeeStatistics
} from '@/api/classHour/hourStat'
export default {
name: 'SubsidyIndex',
data() {
return {
// ==================== 顶部提示条 ====================
tipText: '提示:"课时核算"和"补助核算"要花费稍微长一点时间,操作后请耐心等待系统"操作成功"的提示!',
// ==================== Tab ====================
activeTab: 'hour',
// ==================== 下拉选项 ====================
subsidyStandardOptions: ['2022年秋季学期', '2022年春季学期', '2021年秋季学期', '2021年春季学期'],
accountStandardOptions: ['2022年秋季学期课时补助核算', '2022年春季学期课时补助核算', '2021年秋季学期课时补助核算', '2021年春季学期课时补助核算'],
taskCategoryOptions: [
{ label: '全军院校训练规划内任务', value: '全军院校训练规划内任务' },
{ label: '其他任务(含研究生导师指导)', value: '其他任务(含研究生导师指导)' }
],
// ==================== 新增表单 ====================
form: {
semesterName: '',
taskCategory: '全军院校训练规划内任务',
startDate: '',
endDate: '',
subsidyStandard: '2022年秋季学期',
accountStandard: '2022年秋季学期课时补助核算'
// ==================== 课时统计查询条件 ====================
hourForm: {
nd: '',
xq: '',
jyxm: ''
},
rules: {
semesterName: [{ required: true, message: '请输入学期名称', trigger: 'blur' }],
startDate: [{ required: true, message: '请输入开始日期', trigger: 'blur' }],
endDate: [{ required: true, message: '请输入结束日期', trigger: 'blur' }]
// ==================== 课时费统计查询条件 ====================
feeForm: {
nd: '',
xq: '',
jyxm: '',
ksfbzbh: ''
},
addLoading: false,
// ==================== 表格数据 ====================
tableData: [],
queryLoading: false,
// ==================== 详情对话框 ====================
detailVisible: false,
detailRow: null
// ==================== 各 Tab 列表状态 ====================
hourState: this.createEmptyState(),
feeState: this.createEmptyState()
}
},
computed: {
totalHours() {
return this.tableData.reduce((sum, r) => sum + Number(r.totalHours || 0), 0)
currentForm() {
return this.activeTab === 'hour' ? this.hourForm : this.feeForm
},
totalQualityHours() {
return this.tableData.reduce((sum, r) => sum + Number(r.qualityHours || 0), 0)
},
totalSubsidy() {
return this.tableData.reduce((sum, r) => sum + Number(r.totalSubsidy || 0), 0)
currentState() {
return this.activeTab === 'hour' ? this.hourState : this.feeState
}
},
mounted() {
this.handleQuery()
this.fetchList()
},
methods: {
// ==================== 查询核算列表 ====================
// TODO: 后端接口未提供,暂用模拟数据;接口就绪后替换为 /log/summary/list
handleQuery() {
this.queryLoading = true
setTimeout(() => {
this.tableData = mockSummaryData.map((item, idx) => ({
id: idx + 1,
_bh: item.bh || '',
semesterName: item.mc || '-',
taskCategory: item.rwlb || '-',
startDate: (item.hsksrq || '').substring(0, 10) || '-',
endDate: (item.hsjsrq || '').substring(0, 10) || '-',
totalHours: item.zksl || 0,
qualityHours: item.yzksl || 0,
qualityRatio: item.zksl > 0 ? ((item.yzkslbl || 0) * 100).toFixed(2) + '%' : '0.00%',
totalSubsidy: item.zbzje || 0,
accountStandard: item.mc || '-',
subsidyStandard: '-',
status: item.zt || '-'
}))
this.$message.success(`查询完成,共 ${this.tableData.length} 条记录(前端模拟)`)
this.queryLoading = false
}, 200)
createEmptyState() {
return { list: [], loading: false, pageNum: 1, pageSize: 20, total: 0 }
},
// ==================== 添加 ====================
// TODO: 后端接口未提供,提交为前端模拟;接口就绪后替换为 /log/subsidy-project/add
handleAdd() {
this.$refs.formRef.validate((valid) => {
if (!valid) {
this.$message.warning('请完善必填项后再提交')
return
}
this.addLoading = true
setTimeout(() => {
this.$message.success('核算提交成功(前端模拟)')
this.handleQuery()
this.addLoading = false
}, 300)
/** 构建查询参数,仅传非空字段(与后端 Query 字段一致) */
buildParams(form) {
const params = {}
if (form.nd && form.nd.trim()) params.nd = form.nd.trim()
if (form.xq && form.xq.trim()) params.xq = form.xq.trim()
if (form.jyxm && form.jyxm.trim()) params.jyxm = form.jyxm.trim()
if (form.ksfbzbh && form.ksfbzbh.trim()) params.ksfbzbh = form.ksfbzbh.trim()
return params
},
// ==================== 查询 ====================
fetchList() {
const state = this.currentState
const form = this.currentForm
const params = Object.assign(
{ pageNum: state.pageNum, pageSize: state.pageSize },
this.buildParams(form)
)
state.loading = true
const req = this.activeTab === 'hour' ? listHourStatistics(params) : listFeeStatistics(params)
req.then(res => {
const data = (res && res.data) || {}
state.list = data.records || []
state.total = data.total || 0
}).catch(() => {
state.list = []
state.total = 0
}).finally(() => {
state.loading = false
})
},
// ==================== 详情对话框 ====================
handleDetail(row) {
this.detailRow = row
this.detailVisible = true
handleTabClick() {
this.currentState.pageNum = 1
this.fetchList()
},
handleQuery() {
this.currentState.pageNum = 1
this.fetchList()
},
handlePageChange(page) {
this.currentState.pageNum = page
this.fetchList()
},
handleSizeChange(size) {
this.currentState.pageSize = size
this.currentState.pageNum = 1
this.fetchList()
},
// ==================== 导出 ====================
handleExport() {
const clean = this.buildParams(this.currentForm)
const isHour = this.activeTab === 'hour'
const req = isHour ? exportHourStatistics(clean) : exportFeeStatistics(clean)
const fileName = isHour ? '课时统计.xlsx' : '课时费统计.xlsx'
req.then(blob => {
saveAs(blob, fileName)
this.$message.success('导出成功')
}).catch(() => {})
},
/** 数值空值显示为 '-'(保留 0) */
fmtValue(val) {
return val === null || val === undefined ? '-' : val
}
}
}
@@ -236,63 +263,37 @@ export default {
margin-bottom: 12px;
}
.top-tip {
color: #f56c6c;
font-size: 13px;
margin-bottom: 16px;
line-height: 1.6;
}
// ==================== 1. 查询条件区域 ====================
.search-card {
margin-bottom: 16px;
.form-title {
font-size: 14px;
font-weight: 600;
color: #303133;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid #ebeef5;
}
.search-form {
.w-full {
width: 100%;
.search-actions-col {
display: flex;
align-items: center;
}
.search-actions {
display: flex;
justify-content: flex-end;
margin-top: 8px;
padding-top: 12px;
border-top: 1px solid #ebeef5;
gap: 12px;
}
}
}
// ==================== 2. 数据表格 ====================
.table-card {
.el-table {
width: 100%;
.table-title {
font-size: 14px;
font-weight: 600;
color: #303133;
margin-bottom: 12px;
}
.table-footer {
.pagination {
display: flex;
flex-wrap: wrap;
gap: 24px;
justify-content: flex-end;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid #ebeef5;
font-size: 12px;
color: #606266;
}
}
.status-done {
color: #67c23a;
}
.status-doing {
color: #e6a23c;
}
}
</style>
+2 -210
View File
@@ -1,89 +1,8 @@
<template>
<div class="app-container home">
<!-- 第一行:信息/日志/公告 -->
<!-- 通知公告 -->
<el-row :gutter="20" class="bottom-row">
<el-col :xs="24" :sm="24" :md="12" :lg="8">
<el-card class="module-card info-card" shadow="hover">
<div slot="header" class="card-header">
<span><i class="el-icon-phone-outline card-icon"></i> 联系信息</span>
</div>
<div class="card-body">
<div class="contact-item">
<div class="contact-icon"><i class="el-icon-s-promotion"></i></div>
<div class="contact-text">
<div class="label">官网</div>
<el-link href="http://www.roomroot.vip" target="_blank">http://www.roomroot.vip</el-link>
</div>
</div>
<div class="contact-item">
<div class="contact-icon"><i class="el-icon-user-solid"></i></div>
<div class="contact-text">
<div class="label">QQ群</div>
<a href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=M9y5NjAl44lAL_Vh2crmEehZU_PMU6KS&authKey=ZSDz8hEREWSaPuxQV3gEwqGIaGjfRNnkB4rJjf0IvXhrSUGSGwQFmBA%2Boe8HFxyl&noverify=0&group_code=127358632" target="_blank">127358632</a>
</div>
</div>
<div class="contact-item">
<div class="contact-icon"><i class="el-icon-chat-dot-round"></i></div>
<div class="contact-text">
<div class="label">微信</div>
<span>风影随行</span>
</div>
</div>
<div class="contact-item">
<div class="contact-icon"><i class="el-icon-money"></i></div>
<div class="contact-text">
<div class="label">支付宝</div>
<span>风影随行</span>
</div>
</div>
</div>
</el-card>
</el-col>
<el-col :xs="24" :sm="24" :md="12" :lg="8">
<el-card class="module-card log-card" shadow="hover">
<div slot="header" class="card-header">
<span><i class="el-icon-time card-icon"></i> 登录日志</span>
</div>
<div class="card-body" v-loading="loginLogLoading">
<table v-if="loginLogList.length > 0" class="data-table">
<thead>
<tr>
<th>登录人</th>
<th>登录时间</th>
</tr>
</thead>
<tbody>
<tr v-for="item in loginLogList" :key="item.infoId">
<td>
<i class="el-icon-user table-icon"></i>
{{ item.userName }}
</td>
<td>{{ parseTime(item.loginTime) }}</td>
</tr>
</tbody>
</table>
<div v-else class="empty-state">
<i class="el-icon-folder-opened empty-icon"></i>
<p>暂无记录</p>
</div>
</div>
<div class="card-footer">
<el-pagination
@size-change="handleLoginLogSizeChange"
@current-change="handleLoginLogCurrentChange"
:current-page="loginLogPage.currentPage"
:page-sizes="[10, 20, 50, 100]"
:page-size="loginLogPage.pageSize"
:total="loginLogPage.total"
layout="total, prev, pager, next"
small
/>
</div>
</el-card>
</el-col>
<el-col :xs="24" :sm="24" :md="12" :lg="8">
<el-col :xs="24" :sm="24" :md="24" :lg="24">
<el-card class="module-card notice-card" shadow="hover">
<div slot="header" class="card-header">
<span><i class="el-icon-bell card-icon"></i> 通知公告</span>
@@ -131,7 +50,6 @@
<script>
import { listNotice } from "@/api/system/notice"
import { homeList } from "@/api/monitor/logininfor"
import NoticeDetailView from "@/layout/components/HeaderNotice/DetailView"
export default {
@@ -142,13 +60,6 @@ export default {
version: "3.9.2",
noticeList: [],
noticeLoading: false,
loginLogList: [],
loginLogLoading: false,
loginLogPage: {
currentPage: 1,
pageSize: 10,
total: 0
},
noticePage: {
currentPage: 1,
pageSize: 10,
@@ -158,7 +69,6 @@ export default {
},
mounted() {
this.loadNoticeList()
this.loadLoginLogList()
},
methods: {
goTarget(href) {
@@ -208,28 +118,6 @@ export default {
handleViewNotice(item) {
this.$refs.noticeViewRef.open(item)
},
loadLoginLogList() {
this.loginLogLoading = true
homeList({
pageNum: this.loginLogPage.currentPage,
pageSize: this.loginLogPage.pageSize
}).then(response => {
this.loginLogList = response.rows || []
this.loginLogPage.total = response.total || 0
this.loginLogLoading = false
}).catch(() => {
this.loginLogLoading = false
})
},
handleLoginLogSizeChange(val) {
this.loginLogPage.pageSize = val
this.loginLogPage.currentPage = 1
this.loadLoginLogList()
},
handleLoginLogCurrentChange(val) {
this.loginLogPage.currentPage = val
this.loadLoginLogList()
},
handleNoticeSizeChange(val) {
this.noticePage.pageSize = val
this.noticePage.currentPage = 1
@@ -371,102 +259,6 @@ export default {
}
}
.contact-item {
display: flex;
align-items: center;
padding: 14px 0;
border-bottom: 1px solid #f5f5f5;
&:last-child {
border-bottom: none;
}
.contact-icon {
width: 36px;
height: 36px;
border-radius: 50%;
background: linear-gradient(135deg, var(--edu-green-dark) 0%, var(--edu-green-primary) 100%);
color: #ffffff;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
margin-right: 12px;
flex-shrink: 0;
box-shadow: 0 2px 8px rgba(15, 25, 86, 0.2);
}
.contact-text {
flex: 1;
min-width: 0;
.label {
font-size: 12px;
color: #909399;
margin-bottom: 2px;
}
a, .el-link {
font-size: 13px;
color: var(--edu-green-primary);
}
a:hover, .el-link:hover {
color: var(--edu-green-dark);
}
span {
font-size: 13px;
color: #606266;
}
}
}
.data-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
thead {
tr {
background: linear-gradient(135deg, rgba(0, 135, 90, 0.06) 0%, rgba(0, 135, 90, 0.04) 100%);
}
th {
padding: 10px 20px;
text-align: left;
font-weight: 600;
color: var(--edu-green-dark);
border-bottom: 1px solid rgba(0, 135, 90, 0.1);
}
}
tbody {
tr {
transition: background 0.2s;
&:hover {
background: rgba(0, 135, 90, 0.04);
}
td {
padding: 10px 20px;
color: #4a4f6b;
border-bottom: 1px solid #f0f1f7;
.table-icon {
color: var(--edu-green-light);
margin-right: 6px;
}
}
&:last-child td {
border-bottom: none;
}
}
}
}
.notice-list {
.notice-item {
padding: 14px 0;
+2 -3
View File
@@ -1,4 +1,4 @@
<template>
<template>
<div class="login">
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
<h3 class="title">{{title}}</h3>
@@ -50,14 +50,13 @@
<script>
import Cookies from "js-cookie"
import { encrypt, decrypt } from '@/utils/jsencrypt'
import defaultSettings from '@/settings'
export default {
name: "Login",
data() {
return {
title: process.env.VUE_APP_TITLE,
footerContent: defaultSettings.footerContent,
footerContent: "Copyright © 2018-2026 roomroot. All Rights Reserved.",
loginForm: {
username: "admin",
password: "admin123",
+2 -3
View File
@@ -1,4 +1,4 @@
<template>
<template>
<div class="register">
<el-form ref="registerForm" :model="registerForm" :rules="registerRules" class="register-form">
<h3 class="title">{{title}}</h3>
@@ -55,14 +55,13 @@
<script>
import { register } from "@/api/login"
import passwordRule from "@/utils/passwordRule"
import defaultSettings from '@/settings'
export default {
mixins: [passwordRule],
data() {
return {
title: process.env.VUE_APP_TITLE,
footerContent: defaultSettings.footerContent,
footerContent: "Copyright © 2018-2026 roomroot. All Rights Reserved.",
registerForm: {
username: "",
password: "",
@@ -69,12 +69,6 @@
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<div class="form-tip">注意:勾选"选择框"表示启用该项对应的查询条件。</div>
</el-col>
</el-row>
<el-row>
<el-col :span="24" class="search-actions">
<el-button type="primary" @click="handleSearch">查询</el-button>
@@ -101,9 +95,9 @@
</div>
<div class="bar-right">
<el-button type="primary" @click="handleImportToSystem" :loading="importing">缓存数据导入到系统</el-button>
<el-button type="primary" @click="handleUploadToCache">上传数据至缓存</el-button>
<el-button type="primary" @click="handleCheckCache">检查缓存数据</el-button>
<el-button type="primary" @click="handleImportToSystem">缓存数据导入到系统</el-button>
<el-button type="danger" @click="handleDeleteCache">删除缓存数据</el-button>
</div>
</div>
@@ -112,10 +106,10 @@
<!-- ==================== 3. 数据表格 ==================== -->
<el-card shadow="never" class="table-card">
<el-table v-loading="loading" :data="tableData" stripe border highlight-current-row class="import-table">
<el-table-column prop="xq" label="学期" width="53" align="center" />
<el-table-column prop="nd" label="年度" width="60" align="center" />
<el-table-column prop="xh" label="序号" width="53" align="center" />
<el-table-column prop="yxh" label="原序号" width="67" align="center" />
<el-table-column prop="rq" label="日期" width="53" align="center" :formatter="formatDate" />
<el-table-column prop="rq" label="日期" width="120" align="center" :formatter="formatDate" />
<el-table-column prop="yjc" label="原节次" width="67" align="center" />
<el-table-column prop="jc" label="节次" width="53" align="center" />
<el-table-column prop="kcmc" label="课程名称" width="85" show-overflow-tooltip />
@@ -125,7 +119,6 @@
<el-table-column prop="jxcd" label="教学场地" width="81" align="center" />
<el-table-column prop="jxnr" label="教学内容" width="81" show-overflow-tooltip />
<el-table-column prop="jxff" label="教学方法" width="81" align="center" />
<el-table-column prop="jxyd" label="教学要点" width="81" show-overflow-tooltip />
<el-table-column prop="jybz" label="教员备注" width="81" show-overflow-tooltip />
<el-table-column prop="jwbz" label="教务备注" width="81" show-overflow-tooltip />
<el-table-column prop="jcjg" label="检查结果" width="81" align="center" />
@@ -134,9 +127,9 @@
<span>{{ row.ytgjc ? '是' : '否' }}</span>
</template>
</el-table-column>
<el-table-column prop="ybhhl" label="已被忽略" width="81" align="center">
<el-table-column prop="ybhl" label="已被忽略" width="81" align="center">
<template slot-scope="{ row }">
<span>{{ row.ybhhl ? '是' : '否' }}</span>
<span>{{ row.ybhl ? '是' : '否' }}</span>
</template>
</el-table-column>
<el-table-column prop="ybcl" label="已被处理" width="81" align="center">
@@ -166,12 +159,7 @@
</template>
<script>
// 模拟导入记录数据(后端接口未提供,接口就绪后可删除并改为接口加载)
const mockTableData = [
{ xq: '2026-2027-1', xh: 1, yxh: 1, rq: '2026-09-01 08:00:00', yjc: '1-2', jc: '1', kcmc: '高等数学', bc: '2026级1队', zrdw: '基础部', skjy: '王教员', jxcd: '教学楼101', jxnr: '第一章 函数与极限', jxff: '讲授', jxyd: '理解极限定义', jybz: '', jwbz: '', jcjg: '通过', ytgjc: true, ybhhl: false, ybcl: true, ybdr: false },
{ xq: '2026-2027-1', xh: 2, yxh: 2, rq: '2026-09-01 10:00:00', yjc: '3-4', jc: '2', kcmc: '大学英语', bc: '2026级2队', zrdw: '外语系', skjy: '李教员', jxcd: '教学楼102', jxnr: 'Unit 1 课文讲解', jxff: '讲授', jxyd: '掌握重点词汇', jybz: '', jwbz: '', jcjg: '通过', ytgjc: true, ybhhl: false, ybcl: true, ybdr: false },
{ xq: '2026-2027-1', xh: 3, yxh: 3, rq: '2026-09-02 08:00:00', yjc: '1-2', jc: '1', kcmc: '军事理论', bc: '2026级1队', zrdw: '军事教研室', skjy: '张教员', jxcd: '学术报告厅', jxnr: '国防概论', jxff: '讲授', jxyd: '了解国防常识', jybz: '', jwbz: '', jcjg: '待检', ytgjc: false, ybhhl: false, ybcl: false, ybdr: false }
]
import { listTeachingPlan, importTeachingPlan, downloadTeachingPlanTemplate } from '@/api/schedule/teachingPlan'
export default {
name: 'PlanImportIndex',
@@ -193,50 +181,68 @@ export default {
kmmc: ''
},
// ==================== 文件上传与缓存操作 ====================
// ==================== 文件上传与导入 ====================
selectedFileName: '未选择任何文件',
selectedFile: null,
importing: false,
// ==================== 表格数据 ====================
tableData: [],
loading: false,
allData: [],
pageNum: 1,
pageSize: 20,
total: 0
}
},
mounted() {
this.handleSearch()
this.fetchList()
},
methods: {
// ==================== 查询 ====================
// TODO: 后端接口未提供,暂用模拟数据;接口就绪后替换为接口加载
handleSearch() {
this.loading = true
setTimeout(() => {
this.allData = mockTableData.slice()
this.total = this.allData.length
this.pageNum = 1
this.applyPage()
this.$message.success(`查询完成,共 ${this.allData.length} 条记录(前端模拟)`)
this.loading = false
}, 200)
// 仅传后端 Mapper 支持的字段:skjy(授课教员) bc(班次) kcmc(课程名称) ytgjc(已通过检查)
// 课程时间、忽略状态、教学场地名称后端列表查询不支持,不传
buildSearchParams() {
const f = this.searchForm
const params = { pageNum: this.pageNum, pageSize: this.pageSize }
if (f.jyxm && f.jyxm.trim()) params.skjy = f.jyxm.trim()
if (f.bcmc && f.bcmc.trim()) params.bc = f.bcmc.trim()
if (f.kmmc && f.kmmc.trim()) params.kcmc = f.kmmc.trim()
// 检查状态:仅勾选「已通过」或「未通过」其中之一时才按该条件过滤
if (f.jcztEnabled) {
if (f.jcztYtg && !f.jcztWtg) params.ytgjc = true
else if (!f.jcztYtg && f.jcztWtg) params.ytgjc = false
}
return params
},
applyPage() {
const start = (this.pageNum - 1) * this.pageSize
this.tableData = this.allData.slice(start, start + this.pageSize)
fetchList() {
this.loading = true
listTeachingPlan(this.buildSearchParams()).then(response => {
const data = response.data || {}
this.tableData = data.records || []
this.total = data.total || 0
this.loading = false
}).catch(() => {
this.tableData = []
this.total = 0
this.loading = false
})
},
handleSearch() {
this.pageNum = 1
this.fetchList()
},
handlePageChange(current) {
this.pageNum = current
this.applyPage()
this.fetchList()
},
handleSizeChange(size) {
this.pageSize = size
this.pageNum = 1
this.applyPage()
this.fetchList()
},
/** 日期格式化 */
@@ -244,7 +250,7 @@ export default {
return cellValue ? String(cellValue).substring(0, 10) : ''
},
// ==================== 文件上传与缓存操作 ====================
// ==================== 文件选择 ====================
handleChooseFile() {
this.$refs.fileInputRef && this.$refs.fileInputRef.click()
},
@@ -253,36 +259,68 @@ export default {
const target = event.target
const file = target.files && target.files[0]
if (file) {
this.selectedFile = file
this.selectedFileName = file.name
this.$message.success(`已选择文件:${file.name}`)
} else {
this.selectedFile = null
this.selectedFileName = '未选择任何文件'
}
},
// TODO: 后端接口未提供,以下操作均为前端模拟;接口就绪后替换为对应接口
// ==================== 下载模板 ====================
handleDownloadTemplate() {
this.$message.success('下载课程表数据文件模板(前端模拟)')
downloadTeachingPlanTemplate().then(blob => {
this.downloadBlob(blob, '教学实施计划导入模板.xlsx')
this.$message.success('模板下载成功')
}).catch(() => {})
},
handleUploadToCache() {
if (this.selectedFileName === '未选择任何文件') {
downloadBlob(blob, fileName) {
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = fileName
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
},
// ==================== 导入到系统 ====================
handleImportToSystem() {
if (!this.selectedFile) {
this.$message.warning('请先选择文件')
return
}
this.$message.success('上传数据至缓存(前端模拟)')
this.importing = true
importTeachingPlan(this.selectedFile).then(res => {
const count = res && res.data
if (count !== null && count !== undefined) {
this.$message.success(`导入成功,共 ${count} 条记录`)
} else {
this.$message.success((res && res.msg) || '导入成功')
}
this.selectedFile = null
this.selectedFileName = '未选择任何文件'
this.$refs.fileInputRef && (this.$refs.fileInputRef.value = '')
this.pageNum = 1
this.fetchList()
}).catch(() => {}).finally(() => {
this.importing = false
})
},
handleDeleteCache() {
this.$message.success('删除缓存数据(前端模拟)')
// ==================== 缓存操作(后端暂未提供) ====================
handleUploadToCache() {
this.$message.info('后端暂未提供该接口')
},
handleCheckCache() {
this.$message.success('检查缓存数据(前端模拟)')
this.$message.info('后端暂未提供该接口')
},
handleImportToSystem() {
this.$message.success('缓存数据导入到系统(前端模拟)')
handleDeleteCache() {
this.$message.info('后端暂未提供该接口')
}
}
}
+24 -24
View File
@@ -25,38 +25,38 @@
<el-tabs v-model="activeTab">
<!-- 课程成绩 -->
<el-tab-pane label="课程成绩" name="grades">
<el-table v-loading="loading" :data="gradesData" stripe border>
<el-table v-loading="loading" :data="gradesData" stripe border style="width: 100%">
<el-table-column type="index" label="序号" width="55" align="center" />
<el-table-column prop="xybh" label="学员编号" width="110" show-overflow-tooltip />
<el-table-column prop="kmbh" label="科目编号" width="110" show-overflow-tooltip />
<el-table-column prop="klx" label="课类型" width="90" align="center" />
<el-table-column prop="kscj" label="考试成绩" width="90" align="center" />
<el-table-column prop="pscj" label="平时成绩" width="90" align="center" />
<el-table-column prop="zzcj" label="最终成绩" width="90" align="center" />
<el-table-column prop="bkcj" label="补考成绩" width="90" align="center" />
<el-table-column prop="bkcs" label="补考次数" width="90" align="center" />
<el-table-column prop="ksqk" label="考试情况" width="90" align="center" />
<el-table-column prop="qwxf" label="期望学分" width="90" align="center" />
<el-table-column prop="ytg" label="已通过" width="80" align="center" />
<el-table-column prop="nd" label="年度" width="80" align="center" />
<el-table-column prop="xybh" label="学员编号" min-width="110" show-overflow-tooltip />
<el-table-column prop="kmbh" label="科目编号" min-width="110" show-overflow-tooltip />
<el-table-column prop="klx" label="课类型" min-width="90" align="center" />
<el-table-column prop="kscj" label="考试成绩" min-width="90" align="center" />
<el-table-column prop="pscj" label="平时成绩" min-width="90" align="center" />
<el-table-column prop="zzcj" label="最终成绩" min-width="90" align="center" />
<el-table-column prop="bkcj" label="补考成绩" min-width="90" align="center" />
<el-table-column prop="bkcs" label="补考次数" min-width="90" align="center" />
<el-table-column prop="ksqk" label="考试情况" min-width="90" align="center" />
<el-table-column prop="qwxf" label="期望学分" min-width="90" align="center" />
<el-table-column prop="ytg" label="已通过" min-width="80" align="center" />
<el-table-column prop="nd" label="年度" min-width="80" align="center" />
</el-table>
<el-empty v-if="!loading && gradesData.length === 0" description="暂无课程成绩数据" :image-size="60" />
</el-tab-pane>
<!-- 课程过程成绩 -->
<el-tab-pane label="课程过程成绩" name="process">
<el-table v-loading="loading" :data="processGradesData" stripe border>
<el-table v-loading="loading" :data="processGradesData" stripe border style="width: 100%">
<el-table-column type="index" label="序号" width="55" align="center" />
<el-table-column prop="xybh" label="学员编号" width="110" show-overflow-tooltip />
<el-table-column prop="kmbh" label="科目编号" width="110" show-overflow-tooltip />
<el-table-column prop="klx" label="课类型" width="90" align="center" />
<el-table-column prop="yscj" label="原始成绩" width="90" align="center" />
<el-table-column prop="zzcj" label="最终成绩" width="90" align="center" />
<el-table-column prop="bkcj1" label="补考1" width="80" align="center" />
<el-table-column prop="bkcj2" label="补考2" width="80" align="center" />
<el-table-column prop="bkcj3" label="补考3" width="80" align="center" />
<el-table-column prop="ksqk" label="考试情况" width="90" align="center" />
<el-table-column prop="nd" label="年度" width="80" align="center" />
<el-table-column prop="xybh" label="学员编号" min-width="110" show-overflow-tooltip />
<el-table-column prop="kmbh" label="科目编号" min-width="110" show-overflow-tooltip />
<el-table-column prop="klx" label="课类型" min-width="90" align="center" />
<el-table-column prop="yscj" label="原始成绩" min-width="90" align="center" />
<el-table-column prop="zzcj" label="最终成绩" min-width="90" align="center" />
<el-table-column prop="bkcj1" label="补考1" min-width="80" align="center" />
<el-table-column prop="bkcj2" label="补考2" min-width="80" align="center" />
<el-table-column prop="bkcj3" label="补考3" min-width="80" align="center" />
<el-table-column prop="ksqk" label="考试情况" min-width="90" align="center" />
<el-table-column prop="nd" label="年度" min-width="80" align="center" />
</el-table>
<el-empty v-if="!loading && processGradesData.length === 0" description="暂无课程过程成绩数据" :image-size="60" />
</el-tab-pane>
File diff suppressed because it is too large Load Diff
+404 -182
View File
@@ -1,178 +1,426 @@
<template>
<div class="app-container status-change-page">
<!-- ==================== 1. 查询条件区域 ==================== -->
<!-- ==================== 1. 查询条件 ==================== -->
<el-card shadow="never" class="search-card">
<el-form :model="searchForm" label-width="140px" class="search-form">
<el-form ref="searchFormRef" :model="searchForm" label-width="90px" class="search-form">
<el-row :gutter="24">
<!-- 左栏 -->
<el-col :xs="24" :md="12">
<el-form-item label="年级">
<el-input v-model="searchForm.nj" placeholder="请输入年级" clearable />
</el-form-item>
<el-form-item label="专业">
<el-input v-model="searchForm.zy" placeholder="请输入专业" clearable />
</el-form-item>
<el-form-item>
<template slot="label"><el-checkbox v-model="searchForm.pxlxChecked">培训类型</el-checkbox></template>
<el-select v-model="searchForm.pxlx" class="w-full" :disabled="!searchForm.pxlxChecked">
<el-option v-for="item in pxlxOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item>
<template slot="label"><el-checkbox v-model="searchForm.pxccChecked">培训层次</el-checkbox></template>
<el-select v-model="searchForm.pxcc" class="w-full" :disabled="!searchForm.pxccChecked">
<el-option v-for="item in pxccOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item label="学号">
<el-input v-model="searchForm.xh" placeholder="请输入学号" clearable />
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="编号">
<el-input v-model="searchForm.bh" placeholder="请输入编号" clearable @keyup.enter.native="handleSearch" />
</el-form-item>
</el-col>
<!-- 右栏 -->
<el-col :xs="24" :md="12">
<el-form-item label="队别">
<el-input v-model="searchForm.db" placeholder="请输入队别" clearable />
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="学员编号">
<el-input v-model="searchForm.xybh" placeholder="请输入学员编号" clearable @keyup.enter.native="handleSearch" />
</el-form-item>
<el-form-item label="班次">
<el-input v-model="searchForm.bc" placeholder="请输入班次" clearable />
</el-form-item>
<el-form-item>
<template slot="label"><el-checkbox v-model="searchForm.sqlbChecked">学籍异动申请类别</el-checkbox></template>
<el-select v-model="searchForm.sqlb" class="w-full" :disabled="!searchForm.sqlbChecked">
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="申请类型">
<el-select v-model="searchForm.sqlx" placeholder="请选择申请类型" clearable class="w-full">
<el-option v-for="item in applyTypeOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item>
<template slot="label"><el-checkbox v-model="searchForm.sqztChecked">学籍异动申请状态</el-checkbox></template>
<el-select v-model="searchForm.sqzt" class="w-full" :disabled="!searchForm.sqztChecked">
<el-option v-for="item in applyStatusOptions" :key="item" :label="item" :value="item" />
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="状态">
<el-select v-model="searchForm.zt" placeholder="请选择状态" clearable class="w-full">
<el-option v-for="(item, key) in statusMap" :key="key" :label="item.label" :value="Number(key)" />
</el-select>
</el-form-item>
<el-form-item label="姓名">
<el-input v-model="searchForm.xm" placeholder="请输入姓名" clearable />
</el-form-item>
</el-col>
</el-row>
<div class="search-tip">注意:勾选"选择框"表示启用该项对应的查询条件;文本输入框非空白表示启用对应的查询条件。</div>
<div class="search-actions">
<el-button class="gray-btn" @click="handleSearch">查询</el-button>
</div>
<el-row>
<el-col :span="24" class="search-actions">
<div class="search-buttons">
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button @click="handleReset">重置</el-button>
</div>
</el-col>
</el-row>
</el-form>
</el-card>
<!-- ==================== 2. 审核意见区域 ==================== -->
<el-card shadow="never" class="opinion-card">
<div class="opinion-row">
<span class="opinion-label">审核意见:</span>
<el-input v-model="auditOpinion" placeholder="请输入审核意见" clearable class="opinion-input" />
<el-button type="primary" @click="handleBatchAudit">提交审核意见</el-button>
</div>
</el-card>
<!-- ==================== 3. 列表标题 ==================== -->
<div class="list-title">学籍异动申请审核列表</div>
<!-- ==================== 4. 数据表格区域 ==================== -->
<!-- ==================== 2. 列表 ==================== -->
<el-card shadow="never" class="table-card">
<el-table :data="tableData" stripe border @selection-change="handleSelectionChange">
<el-table-column type="selection" width="40" align="center" />
<el-table-column prop="xhName" label="学号姓名" min-width="150" show-overflow-tooltip />
<el-table-column prop="bcxx" label="班次信息" min-width="160" show-overflow-tooltip />
<el-table-column prop="applyType" label="申请类型" width="90" align="center" />
<el-table-column prop="reason" label="事由" min-width="120" show-overflow-tooltip />
<el-table-column prop="time" label="时间" width="110" align="center" />
<el-table-column prop="auditOpinion" label="审核意见" min-width="120" show-overflow-tooltip />
<el-table-column prop="status" label="状态" width="90" align="center" />
<el-table-column prop="change" label="学籍异动" width="90" align="center" />
<el-table-column label="操作" width="130" align="center" fixed="right">
<div class="table-header">
<span class="table-title">学籍异动申请列表:</span>
<el-button type="primary" icon="el-icon-plus" @click="handleAdd">新增学籍异动申请</el-button>
</div>
<el-table v-loading="loading" :data="tableData" stripe border highlight-current-row>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="bh" label="编号" min-width="140" show-overflow-tooltip />
<el-table-column prop="xybh" label="学员编号" min-width="120" show-overflow-tooltip />
<el-table-column prop="sqlx" label="申请类型" width="100" align="center" />
<el-table-column prop="sy" label="事由" min-width="140" show-overflow-tooltip />
<el-table-column prop="bz" label="备注" min-width="140" show-overflow-tooltip />
<el-table-column label="状态" width="90" align="center">
<template slot-scope="{ row }">
<el-button type="text" size="small" class="text-primary" @click="handleAudit(row)">审核</el-button>
<el-button type="text" size="small" class="text-primary" @click="handleView(row)">查看</el-button>
<el-tag :type="statusTagType(row.zt)" size="mini" effect="light">{{ statusLabel(row.zt) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="提交时间" width="160" align="center">
<template slot-scope="{ row }">{{ row.tjsj ? fmtDateTime(row.tjsj) : '-' }}</template>
</el-table-column>
<el-table-column label="创建时间" width="160" align="center">
<template slot-scope="{ row }">{{ row.cjsj ? fmtDateTime(row.cjsj) : '-' }}</template>
</el-table-column>
<el-table-column label="修改时间" width="160" align="center">
<template slot-scope="{ row }">{{ row.xgsj ? fmtDateTime(row.xgsj) : '-' }}</template>
</el-table-column>
<el-table-column label="操作" width="230" align="center" fixed="right">
<template slot-scope="{ row }">
<el-button v-if="!row.tjsj" type="text" size="small" icon="el-icon-upload2" @click="handleSubmit(row)">提交</el-button>
<el-button v-else type="text" size="small" icon="el-icon-check" disabled>已提交</el-button>
<el-button type="text" size="small" icon="el-icon-view" @click="handleDetail(row)">详情</el-button>
<el-button type="text" size="small" icon="el-icon-edit" @click="handleEdit(row)">编辑</el-button>
<el-button type="text" size="small" class="text-danger" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-empty v-if="!loading && tableData.length === 0" description="暂无学籍异动申请数据" :image-size="60" />
<el-pagination
:current-page="pageNum"
:page-size="pageSize"
:total="total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
class="pagination-wrapper"
@current-change="handlePageChange"
@size-change="handleSizeChange"
/>
</el-card>
<!-- ==================== 3. 新增/编辑对话框 ==================== -->
<el-dialog
:visible="formDialogVisible"
:title="dialogTitle"
width="600px"
:close-on-click-modal="false"
@update:visible="val => formDialogVisible = val"
>
<el-form ref="formRef" :model="form" :rules="formRules" label-width="110px" class="add-form">
<el-form-item label="编号" prop="bh">
<el-input v-model="form.bh" placeholder="请输入编号" :disabled="!isAdd" />
</el-form-item>
<el-form-item label="学员编号" prop="xybh">
<el-input v-model="form.xybh" placeholder="请输入学员编号" />
</el-form-item>
<el-form-item label="申请类型" prop="sqlx">
<el-select v-model="form.sqlx" placeholder="请选择申请类型" class="w-full">
<el-option v-for="item in applyTypeOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item label="事由" prop="sy">
<el-input v-model="form.sy" type="textarea" :rows="3" placeholder="请输入事由" />
</el-form-item>
<el-form-item label="备注">
<el-input v-model="form.bz" type="textarea" :rows="3" placeholder="请输入备注" />
</el-form-item>
<el-form-item label="状态" prop="zt">
<el-select v-model="form.zt" placeholder="请选择状态" class="w-full">
<el-option v-for="(item, key) in statusMap" :key="key" :label="item.label" :value="Number(key)" />
</el-select>
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="formDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="formSaving" @click="handleFormSubmit">确定</el-button>
</div>
</el-dialog>
<!-- ==================== 4. 详情对话框 ==================== -->
<el-dialog title="学籍异动申请详情" :visible="detailDialogVisible" width="760px" :close-on-click-modal="false"
@update:visible="val => detailDialogVisible = val">
<div v-loading="detailLoading" class="detail-body">
<el-descriptions :column="2" border>
<el-descriptions-item label="编号">{{ detailForm.bh || '-' }}</el-descriptions-item>
<el-descriptions-item label="学员编号">{{ detailForm.xybh || '-' }}</el-descriptions-item>
<el-descriptions-item label="申请类型">{{ detailForm.sqlx || '-' }}</el-descriptions-item>
<el-descriptions-item label="状态">
<el-tag :type="statusTagType(detailForm.zt)" size="mini">{{ statusLabel(detailForm.zt) }}</el-tag>
</el-descriptions-item>
<el-descriptions-item label="事由" :span="2">{{ detailForm.sy || '-' }}</el-descriptions-item>
<el-descriptions-item label="备注" :span="2">{{ detailForm.bz || '-' }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ detailForm.cjsj || '-' }}</el-descriptions-item>
<el-descriptions-item label="修改时间">{{ detailForm.xgsj || '-' }}</el-descriptions-item>
<el-descriptions-item label="提交时间">{{ detailForm.tjsj || '-' }}</el-descriptions-item>
<el-descriptions-item label="学员队审核意见">{{ detailForm.xydshyj || '-' }}</el-descriptions-item>
<el-descriptions-item label="学员队干部编号">{{ detailForm.xydgbbh || '-' }}</el-descriptions-item>
<el-descriptions-item label="学员队干部审核时间">{{ detailForm.xydshsj || '-' }}</el-descriptions-item>
<el-descriptions-item label="管理员账户编号">{{ detailForm.glyzhbh || '-' }}</el-descriptions-item>
<el-descriptions-item label="管理员审核意见">{{ detailForm.glyshyj || '-' }}</el-descriptions-item>
<el-descriptions-item label="管理员审核时间">{{ detailForm.glyshsj || '-' }}</el-descriptions-item>
</el-descriptions>
</div>
<div slot="footer">
<el-button @click="detailDialogVisible = false">关闭</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
// 模拟列表数据(后端接口未提供,接口就绪后可删除并改为接口加载)
const mockTableData = [
{ id: 1, xhName: '2025010101 张三', bcxx: '2025级炮兵装备运用54队5区队', applyType: '退学', reason: '身体原因', time: '2026-08-20', auditOpinion: '待审核', status: '拟制', change: '退学' },
{ id: 2, xhName: '2025010102 李四', bcxx: '2025级通信工程32队1区队', applyType: '休学', reason: '家庭原因', time: '2026-08-21', auditOpinion: '同意', status: '待审核', change: '休学' },
{ id: 3, xhName: '2024010101 王五', bcxx: '2024级军事指挥51队2区队', applyType: '留级', reason: '成绩不达标', time: '2026-08-22', auditOpinion: '', status: '拟制', change: '留级' }
]
import {
listApplication,
getApplication,
addApplication,
updateApplication,
delApplication
} from '@/api/studentRecords/application'
export default {
name: 'StatusChangeIndex',
data() {
return {
// ==================== 下拉选项(字典数据,接口就绪后可由后端字典加载) ====================
pxlxOptions: ['军官基础教育', '士兵职业教育', '研究生教育', '其他'],
pxccOptions: ['无', '本科', '硕士', '博士'],
applyTypeOptions: ['退学', '休学', '留级', '复学', '转专业'],
applyStatusOptions: ['拟制', '待审核', '已通过', '已驳回'],
// ==================== 申请类型选项(学籍异动) ====================
applyTypeOptions: ['休学', '复学', '退学', '转学', '留级', '转专业'],
// ==================== 状态映射:0-待审核 1-已审核 2-驳回等 ====================
statusMap: {
0: { label: '待审核', type: 'warning' },
1: { label: '已审核', type: 'success' },
2: { label: '驳回', type: 'danger' }
},
// ==================== 查询条件 ====================
searchForm: {
nj: '', zy: '', pxlxChecked: false, pxlx: '军官基础教育', pxccChecked: false, pxcc: '无', xh: '',
db: '', bc: '', sqlbChecked: false, sqlb: '退学', sqztChecked: false, sqzt: '拟制', xm: ''
bh: '',
xybh: '',
sqlx: '',
zt: undefined
},
// ==================== 审核意见 ====================
auditOpinion: '',
// ==================== 数据表格 ====================
// ==================== 列表数据 ====================
loading: false,
tableData: [],
selectedRows: []
total: 0,
pageNum: 1,
pageSize: 20,
// ==================== 新增/编辑 ====================
formDialogVisible: false,
dialogTitle: '新增学籍异动申请',
isAdd: true,
formSaving: false,
form: this.createEmptyForm(),
formRules: {
bh: [{ required: true, message: '请输入编号', trigger: 'blur' }],
xybh: [{ required: true, message: '请输入学员编号', trigger: 'blur' }],
sqlx: [{ required: true, message: '请选择申请类型', trigger: 'change' }],
sy: [{ required: true, message: '请输入事由', trigger: 'blur' }],
zt: [{ required: true, message: '请选择状态', trigger: 'change' }]
},
// ==================== 详情 ====================
detailDialogVisible: false,
detailLoading: false,
detailForm: {}
}
},
mounted() {
this.loadData()
this.fetchList()
},
methods: {
// ==================== 列表加载 ====================
// TODO: 后端接口未提供,暂用模拟数据;接口就绪后替换为 getStatusChangeList
loadData() {
this.tableData = mockTableData.slice()
createEmptyForm() {
return {
bh: '',
xybh: '',
sqlx: '',
sy: '',
bz: '',
zt: 0
}
},
// ==================== 状态展示 ====================
// 统一格式化后端 LocalDateTime(去除 T、截断到秒)
fmtDateTime(val) {
if (val === null || val === undefined || val === '') return ''
return String(val).replace('T', ' ').slice(0, 19)
},
statusLabel(zt) {
const item = this.statusMap[zt]
return item ? item.label : (zt !== null && zt !== undefined ? String(zt) : '-')
},
statusTagType(zt) {
const item = this.statusMap[zt]
return item ? item.type : 'info'
},
// ==================== 查询列表 ====================
fetchList() {
this.loading = true
const params = {
pageNum: this.pageNum,
pageSize: this.pageSize
}
// 等值筛选,留空不传
;['bh', 'xybh', 'sqlx'].forEach(key => {
const value = this.searchForm[key]
if (value !== '' && value !== null && value !== undefined) {
params[key] = String(value).trim()
}
})
if (this.searchForm.zt !== undefined && this.searchForm.zt !== '' && this.searchForm.zt !== null) {
params.zt = this.searchForm.zt
}
listApplication(params).then(response => {
const data = response.data || {}
this.tableData = data.records || []
this.total = data.total || 0
this.loading = false
}).catch(() => {
this.tableData = []
this.total = 0
this.loading = false
})
},
// ==================== 查询 ====================
// TODO: 后端接口未提供,查询为前端模拟;接口就绪后替换为后端查询
handleSearch() {
this.loadData()
this.$message.success('查询完成(前端模拟)')
this.pageNum = 1
this.fetchList()
},
handleSelectionChange(rows) {
this.selectedRows = rows
handleReset() {
this.$refs.searchFormRef.resetFields()
this.pageNum = 1
this.fetchList()
},
// TODO: 后端接口未提供,审核为前端模拟;接口就绪后替换为 auditStatusChange
handleAudit(row) {
this.$message.info(`审核「${row.xhName}」的申请(前端模拟)`)
handlePageChange(current) {
this.pageNum = current
this.fetchList()
},
// TODO: 后端接口未提供,查看为前端模拟;接口就绪后替换为 getStatusChangeDetail
handleView(row) {
this.$message.info(`查看「${row.xhName}」的申请详情(前端模拟)`)
handleSizeChange(size) {
this.pageSize = size
this.pageNum = 1
this.fetchList()
},
/** 批量审核所选 */
// TODO: 后端接口未提供,批量审核为前端模拟;接口就绪后替换为 batchAuditStatusChange
handleBatchAudit() {
if (this.selectedRows.length === 0) {
this.$message.warning('请先选择申请记录')
return
// ==================== 新增/编辑 ====================
handleAdd() {
this.isAdd = true
this.dialogTitle = '新增学籍异动申请'
this.form = this.createEmptyForm()
this.formDialogVisible = true
this.$nextTick(() => {
this.$refs.formRef && this.$refs.formRef.clearValidate()
})
},
handleEdit(row) {
this.isAdd = false
this.dialogTitle = '编辑学籍异动申请'
this.form = this.createEmptyForm()
this.formDialogVisible = true
this.$nextTick(() => {
this.$refs.formRef && this.$refs.formRef.clearValidate()
})
getApplication(row.bh).then(response => {
const data = response.data || {}
this.form = {
bh: data.bh || '',
xybh: data.xybh || '',
sqlx: data.sqlx || '',
sy: data.sy || '',
bz: data.bz || '',
zt: data.zt !== null && data.zt !== undefined ? Number(data.zt) : 0
}
}).catch(() => {})
},
buildPayload() {
const f = this.form
const payload = {
bh: f.bh,
xybh: f.xybh,
sqlx: f.sqlx,
sy: f.sy,
zt: Number(f.zt)
}
if (!this.auditOpinion.trim()) {
this.$message.warning('请输入审核意见')
return
// 备注留空则移除
if (f.bz !== '' && f.bz !== null && f.bz !== undefined) {
payload.bz = f.bz
}
const names = this.selectedRows.map((r) => r.xhName).join('、')
this.$message.success(`已对「${names}」提交审核意见(前端模拟)`)
return payload
},
handleFormSubmit() {
this.$refs.formRef.validate(valid => {
if (!valid) return
this.formSaving = true
const payload = this.buildPayload()
const request = this.isAdd ? addApplication(payload) : updateApplication(payload)
request.then(() => {
this.$message.success(this.isAdd ? '新增成功' : '修改成功')
this.formDialogVisible = false
this.fetchList()
}).catch(() => {
}).finally(() => {
this.formSaving = false
})
})
},
// ==================== 提交 ====================
// 无独立提交接口,通过编辑接口写入提交时间 tjsj(zt 保持原值不变)
formatNow() {
const d = new Date()
const pad = n => String(n).padStart(2, '0')
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
},
handleSubmit(row) {
this.$confirm(`确定要提交编号为「${row.bh}」的学籍异动申请吗?`, '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
const payload = {
bh: row.bh,
xybh: row.xybh,
sqlx: row.sqlx,
sy: row.sy,
zt: row.zt !== null && row.zt !== undefined ? Number(row.zt) : 0,
tjsj: this.formatNow()
}
if (row.bz) {
payload.bz = row.bz
}
return updateApplication(payload)
}).then(() => {
this.$message.success('提交成功')
this.fetchList()
}).catch(() => {})
},
// ==================== 删除 ====================
handleDelete(row) {
this.$confirm(`确定要删除编号为「${row.bh}」的学籍异动申请吗?`, '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
return delApplication(row.bh)
}).then(() => {
this.$message.success('删除成功')
this.fetchList()
}).catch(() => {})
},
// ==================== 详情 ====================
handleDetail(row) {
this.detailDialogVisible = true
this.detailLoading = true
this.detailForm = {}
getApplication(row.bh).then(response => {
this.detailForm = response.data || {}
this.detailLoading = false
}).catch(() => {
this.detailLoading = false
})
}
}
}
@@ -186,87 +434,61 @@ export default {
.status-change-page {
.search-card {
margin-bottom: 16px;
}
.search-form {
.w-full {
width: 100%;
}
.search-form {
.w-full {
width: 100%;
}
.search-actions {
display: flex;
align-items: center;
justify-content: space-between;
padding-top: 8px;
.search-tip {
font-size: 12px;
color: #909399;
margin-top: 8px;
margin-bottom: 8px;
}
.search-actions {
.search-buttons {
display: flex;
justify-content: flex-end;
padding-top: 12px;
border-top: 1px solid #ebeef5;
gap: 12px;
}
}
}
.opinion-card {
margin-bottom: 16px;
.opinion-row {
display: flex;
align-items: center;
gap: 12px;
.opinion-label {
font-size: 14px;
font-weight: 600;
color: #303133;
white-space: nowrap;
}
.opinion-input {
flex: 1;
min-width: 0;
}
}
}
.list-title {
font-size: 17px;
font-weight: 700;
color: #000;
margin-bottom: 12px;
}
.table-card {
.el-table {
width: 100%;
.table-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
::v-deep(.cell) {
font-size: 12px;
.table-title {
font-size: 16px;
font-weight: 600;
color: #303133;
}
}
}
.text-primary {
color: #409eff;
padding: 0;
}
.gray-btn {
height: 28px;
padding: 2px 16px;
font-size: 12px;
background: #f5f5f5;
border: 1px solid #c0c4cc;
color: #333;
border-radius: 2px;
&:hover,
&:focus {
background: #ebebeb;
border-color: #c0c4cc;
color: #333;
.pagination-wrapper {
display: flex;
justify-content: flex-end;
margin-top: 16px;
}
}
.add-form {
max-height: 60vh;
overflow-y: auto;
padding-right: 4px;
}
.text-danger {
color: #f56c6c;
padding: 0;
}
}
</style>
+29 -149
View File
@@ -6,67 +6,11 @@
<el-row :gutter="24">
<!-- 左栏 -->
<el-col :xs="24" :md="12">
<el-form-item label="年级">
<el-input v-model="searchForm.nj" placeholder="请输入年级" clearable />
</el-form-item>
<el-form-item label="专业">
<el-input v-model="searchForm.zy" placeholder="请输入专业" clearable />
</el-form-item>
<el-form-item>
<template slot="label"><el-checkbox v-model="searchForm.pxlxChecked">培训类型</el-checkbox></template>
<el-select v-model="searchForm.pxlx" class="w-full" :disabled="!searchForm.pxlxChecked">
<el-option v-for="item in pxlxOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item>
<template slot="label"><el-checkbox v-model="searchForm.pxccChecked">培训层次</el-checkbox></template>
<el-select v-model="searchForm.pxcc" class="w-full" :disabled="!searchForm.pxccChecked">
<el-option v-for="item in pxccOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item label="学号">
<el-input v-model="searchForm.xh" placeholder="请输入学号" clearable />
</el-form-item>
<el-form-item label="证件号码">
<el-input v-model="searchForm.zjhm" placeholder="请输入证件号码" clearable />
</el-form-item>
<el-form-item label="政治面貌">
<el-input v-model="searchForm.zzmm" placeholder="请输入政治面貌" clearable />
</el-form-item>
<el-form-item label="籍贯">
<el-input v-model="searchForm.jg" placeholder="请输入籍贯" clearable />
</el-form-item>
<el-form-item label="学位">
<el-input v-model="searchForm.xw" placeholder="请输入学位" clearable />
</el-form-item>
<el-form-item label="原部职别">
<el-input v-model="searchForm.ybzb" placeholder="请输入原部职别" clearable />
</el-form-item>
<el-form-item label="标签">
<el-input v-model="searchForm.bq" placeholder="请输入标签" clearable />
</el-form-item>
<el-form-item>
<template slot="label"><el-checkbox v-model="searchForm.xjydEnabled">学籍异动状态</el-checkbox></template>
<el-radio-group v-model="searchForm.xjyd" :disabled="!searchForm.xjydEnabled">
<el-radio :label="'无异动'">无异动</el-radio>
<el-radio :label="'异动'">异动</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item>
<template slot="label"><el-checkbox v-model="searchForm.ksqkEnabled">考试情况</el-checkbox></template>
<el-radio-group v-model="searchForm.ksqk" :disabled="!searchForm.ksqkEnabled">
<el-radio :label="'正常'">正常</el-radio>
<el-radio :label="'异动'">异动</el-radio>
<el-radio :label="'旷考'">旷考</el-radio>
<el-radio :label="'作弊'">作弊</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="不及格门数">
<div class="range-control">
<el-input v-model="searchForm.bjgmMin" placeholder="0" />
<span class="range-sep">至</span>
<el-input v-model="searchForm.bjgmMax" placeholder="0" />
</div>
<el-form-item label="姓名">
<el-input v-model="searchForm.xm" placeholder="请输入姓名" clearable />
</el-form-item>
<el-form-item>
<template slot="label"><el-checkbox v-model="searchForm.xbEnabled">性别</el-checkbox></template>
@@ -75,84 +19,49 @@
<el-radio :label="'女'">女</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item>
<template slot="label"><el-checkbox v-model="searchForm.zcztEnabled">注册状态</el-checkbox></template>
<el-radio-group v-model="searchForm.zczt" :disabled="!searchForm.zcztEnabled">
<el-radio :label="'已注册'">已注册</el-radio>
<el-radio :label="'未注册'">未注册</el-radio>
</el-radio-group>
<el-form-item label="证件号码">
<el-input v-model="searchForm.zjhm" placeholder="请输入证件号码" clearable />
</el-form-item>
<el-form-item label="政治面貌">
<el-input v-model="searchForm.zzmm" placeholder="请输入政治面貌" clearable />
</el-form-item>
<el-form-item label="民族">
<el-input v-model="searchForm.mz" placeholder="请输入民族" clearable />
</el-form-item>
<el-form-item label="籍贯">
<el-input v-model="searchForm.jg" placeholder="请输入籍贯" clearable />
</el-form-item>
<el-form-item label="学位">
<el-input v-model="searchForm.xw" placeholder="请输入学位" clearable />
</el-form-item>
</el-col>
<!-- 右栏 -->
<el-col :xs="24" :md="12">
<el-form-item label="队别">
<el-input v-model="searchForm.db" placeholder="请输入队别" clearable />
</el-form-item>
<el-form-item label="班次">
<el-input v-model="searchForm.bc" placeholder="请输入班次" clearable />
</el-form-item>
<el-form-item>
<template slot="label"><el-checkbox v-model="searchForm.pxlx2Checked">培训类型2</el-checkbox></template>
<el-select v-model="searchForm.pxlx2" class="w-full" :disabled="!searchForm.pxlx2Checked">
<el-option v-for="item in pxlx2Options" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item label="自定义分类">
<el-input v-model="searchForm.zdyfl" placeholder="请输入自定义分类" clearable />
</el-form-item>
<el-form-item label="姓名">
<el-input v-model="searchForm.xm" placeholder="请输入姓名" clearable />
<el-form-item label="原部职别">
<el-input v-model="searchForm.ybzb" placeholder="请输入原部职别" clearable />
</el-form-item>
<el-form-item label="身份证号">
<el-input v-model="searchForm.sfzh" placeholder="请输入身份证号" clearable />
</el-form-item>
<el-form-item label="民族">
<el-input v-model="searchForm.mz" placeholder="请输入民族" clearable />
</el-form-item>
<el-form-item label="文化程度">
<el-input v-model="searchForm.whcd" placeholder="请输入文化程度" clearable />
</el-form-item>
<el-form-item label="本科毕业院校">
<el-input v-model="searchForm.bkbyyx" placeholder="请输入本科毕业院校" clearable />
</el-form-item>
<el-form-item label="所属战区">
<el-input v-model="searchForm.sszq" placeholder="请输入所属战区" clearable />
<el-form-item label="标签">
<el-input v-model="searchForm.bq" placeholder="请输入标签" clearable />
</el-form-item>
<el-form-item label="奖惩情况">
<el-input v-model="searchForm.jcqk" placeholder="请输入奖惩情况" clearable />
<el-form-item label="联系电话">
<el-input v-model="searchForm.lxdh" placeholder="请输入联系电话" clearable />
</el-form-item>
<el-form-item>
<template slot="label"><el-checkbox v-model="searchForm.txEnabled">退学状态</el-checkbox></template>
<el-radio-group v-model="searchForm.tx" :disabled="!searchForm.txEnabled">
<el-radio :label="'未退学'">未退学</el-radio>
<el-radio :label="'退学'">退学</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="曾不及格门数">
<div class="range-control">
<el-input v-model="searchForm.lbjgmMin" placeholder="0" />
<span class="range-sep">至</span>
<el-input v-model="searchForm.lbjgmMax" placeholder="0" />
</div>
</el-form-item>
<el-form-item label="补考不及格门数">
<div class="range-control">
<el-input v-model="searchForm.bkcljgmMin" placeholder="0" />
<span class="range-sep">至</span>
<el-input v-model="searchForm.bkcljgmMax" placeholder="0" />
</div>
</el-form-item>
<el-form-item>
<template slot="label"><el-checkbox v-model="searchForm.gljgChecked">管理机构</el-checkbox></template>
<el-select v-model="searchForm.gljg" class="w-full" :disabled="!searchForm.gljgChecked">
<el-option v-for="item in gljgOptions" :key="item" :label="item" :value="item" />
</el-select>
<el-form-item label="通信地址">
<el-input v-model="searchForm.txdz" placeholder="请输入通信地址" clearable />
</el-form-item>
</el-col>
</el-row>
<div class="search-tip">注意:勾选"选择框"表示启用该项对应的查询条件;文本输入框非空白表示启用对应的查询条件。</div>
<div class="search-actions">
<el-button class="gray-btn" @click="handleSearch">查询</el-button>
</div>
@@ -312,21 +221,11 @@ export default {
name: 'StudentInfoIndex',
data() {
return {
// ==================== 下拉选项(字典数据,接口就绪后可由后端字典加载) ====================
pxlxOptions: ['军官基础教育', '士兵职业教育', '研究生教育', '其他'],
pxccOptions: ['无', '本科', '硕士', '博士'],
pxlx2Options: ['其他', '军官基础教育', '任职培训', '研究生教育'],
gljgOptions: ['教务处', '教研室1', '教研室2'],
// ==================== 查询条件 ====================
// 仅保留后端 XYXX 实体支持的字段:/student-records/list 会按实体字段动态构建查询条件
searchForm: {
nj: '', zy: '', pxlxChecked: false, pxlx: '军官基础教育', pxccChecked: false, pxcc: '无', xh: '', zjhm: '', zzmm: '',
jg: '', xw: '', ybzb: '', bq: '', xjydEnabled: false, xjyd: '无异动', ksqkEnabled: false, ksqk: '正常',
bjgmMin: '', bjgmMax: '', xbEnabled: false, xb: '男', zcztEnabled: false, zczt: '已注册',
db: '', bc: '', pxlx2Checked: false, pxlx2: '其他', zdyfl: '', xm: '', sfzh: '', mz: '', whcd: '',
bkbyyx: '', sszq: '', jcqk: '', txEnabled: false, tx: '未退学', lbjgmMin: '', lbjgmMax: '',
bkcljgmMin: '', bkcljgmMax: '', gljgChecked: false, gljg: '教务处',
xylb: ''
xh: '', xm: '', xbEnabled: false, xb: '男', zjhm: '', zzmm: '', mz: '', jg: '', xw: '',
ybzb: '', sfzh: '', whcd: '', bkbyyx: '', bq: '', lxdh: '', txdz: ''
},
// ==================== 数据表格 ====================
@@ -406,33 +305,14 @@ export default {
})
},
/** 组装查询参数:文本非空才传,勾选启用的字段才传,门数范围二者其一非空才传 */
/** 组装查询参数:仅传后端 XYXX 实体支持的字符串字段,空值忽略 */
buildQueryParams(params) {
const textFields = ['xh', 'xm', 'sfzh', 'zjhm', 'zzmm', 'mz', 'jg', 'xw', 'ybzb', 'bq', 'whcd', 'bkbyyx', 'xylb', 'zdyfl', 'nj', 'zy', 'db', 'bc', 'sszq', 'jcqk']
const textFields = ['xh', 'xm', 'zjhm', 'zzmm', 'mz', 'jg', 'xw', 'ybzb', 'sfzh', 'whcd', 'bkbyyx', 'bq', 'lxdh', 'txdz']
textFields.forEach(key => {
const v = this.searchForm[key]
if (v !== '' && v !== null && v !== undefined) params[key] = v.trim()
})
if (this.searchForm.pxlxChecked) params.pxlx = this.searchForm.pxlx
if (this.searchForm.pxccChecked) params.pxcc = this.searchForm.pxcc
if (this.searchForm.pxlx2Checked) params.pxlx2 = this.searchForm.pxlx2
if (this.searchForm.xjydEnabled) params.xjyd = this.searchForm.xjyd
if (this.searchForm.ksqkEnabled) params.ksqk = this.searchForm.ksqk
if (this.searchForm.xbEnabled) params.xb = this.searchForm.xb
if (this.searchForm.zcztEnabled) params.zczt = this.searchForm.zczt
if (this.searchForm.txEnabled) params.txzt = this.searchForm.tx
if (this.searchForm.gljgChecked) params.gljg = this.searchForm.gljg
const ranges = [
['bjgmMin', 'bjgmMax'],
['lbjgmMin', 'lbjgmMax'],
['bkcljgmMin', 'bkcljgmMax']
]
ranges.forEach(([minKey, maxKey]) => {
if (this.searchForm[minKey] !== '' || this.searchForm[maxKey] !== '') {
params[minKey] = this.searchForm[minKey]
params[maxKey] = this.searchForm[maxKey]
}
})
},
// ==================== 查询/分页 ====================
@@ -1,15 +1,256 @@
<template>
<div class="app-container">
<placeholder-page title="学员学籍预警结果" icon="checkbox"
description="用于查看学员学籍预警的触发结果列表,支持按条件筛选、导出与处理。" />
<div class="app-container warning-result-page">
<!-- ==================== 1. 查询条件 ==================== -->
<el-card shadow="never" class="search-card">
<el-form ref="searchFormRef" :model="searchForm" label-width="110px" class="search-form">
<el-row :gutter="24">
<el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="8">
<el-form-item label="年度">
<el-input v-model="searchForm.nd" placeholder="请输入年度" clearable @keyup.enter.native="handleSearch" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="8">
<el-form-item label="预警条件编号">
<el-input v-model="searchForm.yjtjbh" placeholder="请输入预警条件编号" clearable @keyup.enter.native="handleSearch" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="8">
<el-form-item label="学员编号">
<el-input v-model="searchForm.xybh" placeholder="请输入学员编号" clearable @keyup.enter.native="handleSearch" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="8">
<el-form-item label="发布">
<el-select v-model="searchForm.fb" placeholder="请选择发布状态" clearable class="w-full">
<el-option label="未发布" :value="0" />
<el-option label="已发布" :value="1" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row class="search-actions-row">
<el-col :span="24" class="search-actions">
<el-button type="primary" icon="el-icon-search" @click="handleSearch">查询</el-button>
<el-button icon="el-icon-refresh" @click="handleReset">重置</el-button>
</el-col>
</el-row>
</el-form>
</el-card>
<!-- ==================== 2. 列表 ==================== -->
<el-card shadow="never" class="table-card">
<div class="table-header">
<span class="table-title">预警结果列表:</span>
</div>
<el-table v-loading="loading" :data="tableData" stripe border highlight-current-row>
<el-table-column prop="bh" label="编号" min-width="140" show-overflow-tooltip />
<el-table-column prop="nd" label="年度" width="90" align="center" />
<el-table-column prop="yjtjbh" label="预警条件编号" min-width="140" show-overflow-tooltip />
<el-table-column prop="xybh" label="学员编号" min-width="120" show-overflow-tooltip />
<el-table-column prop="cjsj" label="创建时间" width="160" show-overflow-tooltip />
<el-table-column label="发布" width="90" align="center">
<template slot-scope="{ row }">
<el-tag :type="Number(row.fb) === 1 ? 'success' : 'info'" size="mini">
{{ Number(row.fb) === 1 ? '是' : '否' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="fbsj" label="发布时间" width="160" show-overflow-tooltip />
<el-table-column label="操作" width="100" align="center" fixed="right">
<template slot-scope="{ row }">
<el-button type="text" size="small" icon="el-icon-view" @click="handleDetail(row)">详情</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
:current-page="pageNum"
:page-size="pageSize"
:total="total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
class="pagination-wrapper"
@current-change="handlePageChange"
@size-change="handleSizeChange"
/>
</el-card>
<!-- ==================== 3. 详情对话框 ==================== -->
<el-dialog title="预警结果详情" :visible="detailDialogVisible" width="560px" :close-on-click-modal="false"
@update:visible="val => detailDialogVisible = val">
<div v-loading="detailLoading" class="detail-body">
<el-descriptions :column="2" border>
<el-descriptions-item label="编号">{{ detailForm.bh || '-' }}</el-descriptions-item>
<el-descriptions-item label="年度">{{ detailForm.nd || '-' }}</el-descriptions-item>
<el-descriptions-item label="预警条件编号">{{ detailForm.yjtjbh || '-' }}</el-descriptions-item>
<el-descriptions-item label="学员编号">{{ detailForm.xybh || '-' }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ detailForm.cjsj || '-' }}</el-descriptions-item>
<el-descriptions-item label="发布时间">{{ detailForm.fbsj || '-' }}</el-descriptions-item>
<el-descriptions-item label="发布">
<el-tag :type="Number(detailForm.fb) === 1 ? 'success' : 'info'" size="mini">
{{ Number(detailForm.fb) === 1 ? '是' : '否' }}
</el-tag>
</el-descriptions-item>
</el-descriptions>
</div>
<div slot="footer">
<el-button @click="detailDialogVisible = false">关闭</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import PlaceholderPage from "@/components/PlaceholderPage"
import {
listWarningResult,
getWarningResult
} from '@/api/studentRecords/warningResult'
export default {
name: "WarningResult",
components: { PlaceholderPage }
name: 'WarningResult',
data() {
return {
// ==================== 查询条件 ====================
searchForm: {
nd: '',
yjtjbh: '',
xybh: '',
fb: undefined
},
// ==================== 列表数据 ====================
loading: false,
tableData: [],
total: 0,
pageNum: 1,
pageSize: 20,
// ==================== 详情 ====================
detailDialogVisible: false,
detailLoading: false,
detailForm: {}
}
},
created() {
this.fetchList()
},
methods: {
// ==================== 查询列表 ====================
fetchList() {
this.loading = true
const params = {
pageNum: this.pageNum,
pageSize: this.pageSize
}
// 文本条件非空时才传
;['nd', 'yjtjbh', 'xybh'].forEach(key => {
const value = this.searchForm[key]
if (value !== '' && value !== null && value !== undefined) {
params[key] = String(value).trim()
}
})
// 发布:等值筛选(选中时才传)
if (this.searchForm.fb !== undefined && this.searchForm.fb !== '' && this.searchForm.fb !== null) {
params.fb = this.searchForm.fb
}
listWarningResult(params).then(response => {
const data = response.data || {}
this.tableData = data.records || []
this.total = data.total || 0
this.loading = false
}).catch(() => {
this.tableData = []
this.total = 0
this.loading = false
})
},
handleSearch() {
this.pageNum = 1
this.fetchList()
},
handleReset() {
this.$refs.searchFormRef.resetFields()
this.pageNum = 1
this.fetchList()
},
handlePageChange(current) {
this.pageNum = current
this.fetchList()
},
handleSizeChange(size) {
this.pageSize = size
this.pageNum = 1
this.fetchList()
},
// ==================== 详情 ====================
handleDetail(row) {
this.detailDialogVisible = true
this.detailLoading = true
this.detailForm = {}
getWarningResult(row.bh).then(response => {
this.detailForm = response.data || {}
this.detailLoading = false
}).catch(() => {
this.detailLoading = false
})
}
}
}
</script>
<style scoped lang="scss">
.app-container {
padding: 20px;
}
.warning-result-page {
.search-card {
margin-bottom: 16px;
}
.search-form {
.w-full {
width: 100%;
}
.search-actions-row {
margin-top: 2px;
border-top: 1px dashed #ebeef5;
padding-top: 14px;
}
.search-actions {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 12px;
}
}
.table-card {
.table-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
.table-title {
font-size: 16px;
font-weight: 600;
color: #303133;
}
}
.pagination-wrapper {
display: flex;
justify-content: flex-end;
margin-top: 16px;
}
}
}
</style>
@@ -6,22 +6,22 @@
<el-row :gutter="24">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="专业名称">
<el-input v-model="searchForm.zymc" placeholder="请输入专业名称" clearable />
<el-input v-model="searchForm.zymc" placeholder="请输入专业名称" clearable @keyup.enter.native="handleSearch" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="专业代码">
<el-input v-model="searchForm.zydm" placeholder="请输入专业代码" clearable />
<el-input v-model="searchForm.zydm" placeholder="请输入专业代码" clearable @keyup.enter.native="handleSearch" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="专业方向">
<el-input v-model="searchForm.zyfx" placeholder="请输入专业方向" clearable />
<el-input v-model="searchForm.zyfx" placeholder="请输入专业方向" clearable @keyup.enter.native="handleSearch" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="学年制">
<el-input v-model="searchForm.xnz" placeholder="请输入学年制" clearable />
<el-input v-model="searchForm.xnz" placeholder="请输入学年制" clearable @keyup.enter.native="handleSearch" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
@@ -41,7 +41,6 @@
</el-row>
<el-row>
<el-col :span="24" class="search-actions">
<div class="search-tip">注意:"勾选框" 表示启用该项对应的查询条件。</div>
<div class="search-buttons">
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button @click="handleReset">重置</el-button>
@@ -55,23 +54,25 @@
<el-card shadow="never" class="table-card">
<div class="table-header">
<span class="table-title">学科专业列表:</span>
<el-button type="primary" @click="handleAdd">新建学科专业</el-button>
<el-button type="primary" icon="el-icon-plus" @click="handleAdd">新建学科专业</el-button>
</div>
<el-table v-loading="loading" :data="tableData" stripe border highlight-current-row>
<el-table-column type="selection" width="55" align="center" />
<el-table-column prop="jxgljgbh" label="教学管理机构" width="120" show-overflow-tooltip />
<el-table-column prop="xh" label="序号" width="60" align="center" />
<el-table-column prop="zydm" label="专业代码" width="85" />
<el-table-column prop="zymc" label="专业名称" show-overflow-tooltip />
<el-table-column prop="rcpyfaCount" label="在用人才培养方案数" width="180" align="center" />
<el-table-column prop="zypyfa" label="在用人才培养方案" width="160" show-overflow-tooltip />
<el-table-column prop="zdyfl" label="分类" width="100" align="center" />
<el-table-column prop="xylb" label="学员类别" width="100" align="center" />
<el-table-column prop="xnz" label="学年制" width="90" align="center" />
<el-table-column prop="xqs" label="学期数" width="90" align="center" />
<el-table-column prop="zgzy" label="主干专业" width="100" align="center" />
<el-table-column label="操作" width="160" align="center" fixed="right">
<el-table-column label="序号" width="60" align="center">
<template slot-scope="scope">{{ fmtXh(scope.$index) }}</template>
</el-table-column>
<el-table-column prop="jxgljgbh" label="教学管理机构" width="200" show-overflow-tooltip />
<el-table-column prop="zydm" label="专业代码" width="200" />
<el-table-column prop="zymc" label="专业名称" width="200" show-overflow-tooltip />
<el-table-column prop="zdyfl" label="分类" width="150" align="center" />
<el-table-column prop="xylb" label="学员类别" width="150" align="center" />
<el-table-column prop="xnz" label="学年制" width="120" align="center" />
<el-table-column prop="xqs" label="学期数" width="120" align="center" />
<el-table-column prop="zgzy" label="主干专业" align="center" />
<el-table-column label="操作" width="230" align="center" fixed="right">
<template slot-scope="{ row }">
<el-button type="text" size="small" icon="el-icon-view" @click="handleDetail(row)">详情</el-button>
<el-button type="text" size="small" icon="el-icon-edit" @click="handleEdit(row)">编辑</el-button>
<el-button v-if="row.ty === 1" type="text" size="small" class="text-success" @click="handleEnable(row)">
启用
</el-button>
@@ -94,125 +95,160 @@
/>
</el-card>
<!-- ==================== 3. 新增学科专业对话框 ==================== -->
<!-- ==================== 3. 新增/编辑学科专业对话框 ==================== -->
<el-dialog
:visible="addDialogVisible"
title="新增学科专业"
:visible="formDialogVisible"
:title="dialogTitle"
width="750px"
:close-on-click-modal="false"
@update:visible="val => addDialogVisible = val"
@update:visible="val => formDialogVisible = val"
>
<el-form :model="addForm" label-width="130px" class="add-form">
<el-form ref="formRef" :model="form" label-width="130px" class="add-form">
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="规范名称">
<el-input v-model="addForm.gfmc" placeholder="请输入规范名称" />
<el-input v-model="form.gfmc" placeholder="请输入规范名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="序号">
<el-input v-model="addForm.xh" placeholder="请输入序号" />
<el-input-number v-model="form.xh" :min="0" controls-position="right" style="width: 100%" placeholder="请输入序号" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="专业代码">
<el-input v-model="addForm.zydm" placeholder="请输入专业代码" />
<el-input v-model="form.zydm" placeholder="请输入专业代码" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="专业名称">
<el-input v-model="addForm.zymc" placeholder="请输入专业名称" />
<el-input v-model="form.zymc" placeholder="请输入专业名称" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="专业方向">
<el-input v-model="addForm.zyfx" placeholder="请输入专业方向" />
<el-input v-model="form.zyfx" placeholder="请输入专业方向" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="学年制">
<el-input v-model="addForm.xnz" placeholder="请输入学年制" />
<el-input v-model="form.xnz" placeholder="请输入学年制" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="学期数">
<el-input v-model="addForm.xqs" placeholder="请输入学期数" />
<el-input-number v-model="form.xqs" :min="0" controls-position="right" style="width: 100%" placeholder="请输入学期数" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="培训层次">
<el-input v-model="addForm.pxcc" placeholder="请输入培训层次" />
<el-input v-model="form.pxcc" placeholder="请输入培训层次" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="培训类型">
<el-input v-model="addForm.pxlx" placeholder="请输入培训类型" />
<el-input v-model="form.pxlx" placeholder="请输入培训类型" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="培训类型2">
<el-input v-model="addForm.pxlx2" placeholder="请输入培训类型2" />
<el-input v-model="form.pxlx2" placeholder="请输入培训类型2" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="教学管理机构编号">
<el-input v-model="addForm.jxgljgbh" placeholder="请输入教学管理机构编号" />
<el-input v-model="form.jxgljgbh" placeholder="请输入教学管理机构编号" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="学员类别">
<el-input v-model="addForm.xylb" placeholder="请输入学员类别" />
<el-input v-model="form.xylb" placeholder="请输入学员类别" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="指导分类">
<el-input v-model="addForm.zdyfl" placeholder="请输入指导分类" />
<el-form-item label="自定义分类">
<el-input v-model="form.zdyfl" placeholder="请输入自定义分类" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="主干专业">
<el-input v-model="addForm.zgzy" placeholder="请输入主干专业" />
<el-input v-model="form.zgzy" placeholder="请输入主干专业" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="JSON字段">
<el-input v-model="addForm.jsonzd" placeholder="请输入JSON字段" />
<el-input v-model="form.jsonzd" placeholder="请输入JSON字段" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="备注">
<el-input v-model="addForm.bz" type="textarea" :rows="3" placeholder="请输入备注" />
<el-input v-model="form.bz" type="textarea" :rows="3" placeholder="请输入备注" />
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="addDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="addSaving" @click="handleAddConfirm">确定</el-button>
<el-button @click="formDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="formSaving" @click="handleFormSubmit">确定</el-button>
</div>
</el-dialog>
<!-- ==================== 4. 详情对话框 ==================== -->
<el-dialog title="学科专业详情" :visible="detailDialogVisible" width="700px" :close-on-click-modal="false"
@update:visible="val => detailDialogVisible = val">
<div v-loading="detailLoading" class="detail-body">
<el-descriptions :column="2" border>
<el-descriptions-item label="规范名称">{{ detailForm.gfmc || '-' }}</el-descriptions-item>
<el-descriptions-item label="序号">{{ fmtValue(detailForm.xh) }}</el-descriptions-item>
<el-descriptions-item label="专业代码">{{ detailForm.zydm || '-' }}</el-descriptions-item>
<el-descriptions-item label="专业名称">{{ detailForm.zymc || '-' }}</el-descriptions-item>
<el-descriptions-item label="专业方向">{{ detailForm.zyfx || '-' }}</el-descriptions-item>
<el-descriptions-item label="学年制">{{ detailForm.xnz || '-' }}</el-descriptions-item>
<el-descriptions-item label="学期数">{{ fmtValue(detailForm.xqs) }}</el-descriptions-item>
<el-descriptions-item label="培训层次">{{ detailForm.pxcc || '-' }}</el-descriptions-item>
<el-descriptions-item label="培训类型">{{ detailForm.pxlx || '-' }}</el-descriptions-item>
<el-descriptions-item label="培训类型2">{{ detailForm.pxlx2 || '-' }}</el-descriptions-item>
<el-descriptions-item label="教学管理机构编号">{{ detailForm.jxgljgbh || '-' }}</el-descriptions-item>
<el-descriptions-item label="学员类别">{{ detailForm.xylb || '-' }}</el-descriptions-item>
<el-descriptions-item label="自定义分类">{{ detailForm.zdyfl || '-' }}</el-descriptions-item>
<el-descriptions-item label="主干专业">{{ detailForm.zgzy || '-' }}</el-descriptions-item>
<el-descriptions-item label="停用">
<el-tag :type="detailForm.ty === 1 ? 'danger' : 'success'" size="mini">
{{ detailForm.ty === 1 ? '是' : '否' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="停用时间">{{ detailForm.tysj || '-' }}</el-descriptions-item>
<el-descriptions-item label="JSON字段" :span="2">{{ detailForm.jsonzd || '-' }}</el-descriptions-item>
<el-descriptions-item label="备注" :span="2">{{ detailForm.bz || '-' }}</el-descriptions-item>
</el-descriptions>
</div>
<div slot="footer">
<el-button @click="detailDialogVisible = false">关闭</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
// 模拟学科专业列表数据(后端接口未提供,接口就绪后可删除并改为接口加载)
const mockTableData = [
{ bsh: 'Z001', jxgljgbh: 'JG001', xh: 1, zydm: '110101', zymc: '军事指挥', rcpyfaCount: 2, zypyfa: '2026版军事指挥人才培养方案', zdyfl: '指挥类', xylb: '生长干部', xnz: '4', xqs: '8', zgzy: '是', ty: 0 },
{ bsh: 'Z002', jxgljgbh: 'JG002', xh: 2, zydm: '110102', zymc: '通信工程', rcpyfaCount: 1, zypyfa: '2025版通信工程人才培养方案', zdyfl: '技术类', xylb: '现职干部', xnz: '3', xqs: '6', zgzy: '是', ty: 1 },
{ bsh: 'Z003', jxgljgbh: 'JG003', xh: 3, zydm: '110103', zymc: '装备维修', rcpyfaCount: 0, zypyfa: '-', zdyfl: '勤务类', xylb: '士官', xnz: '2', xqs: '4', zgzy: '否', ty: 0 }
]
import {
listDiscipline,
getDiscipline,
addDiscipline,
updateDiscipline,
disableDiscipline
} from '@/api/subjectMajor/discipline'
export default {
name: 'DisciplineIndex',
@@ -231,79 +267,87 @@ export default {
// ==================== 列表数据 ====================
loading: false,
tableData: [],
allData: [],
total: 0,
pageNum: 1,
pageSize: 20,
// ==================== 新增学科专业 ====================
addDialogVisible: false,
addSaving: false,
addForm: this.createEmptyAddForm()
// ==================== 新增/编辑 ====================
formDialogVisible: false,
dialogTitle: '新增学科专业',
isAdd: true,
formSaving: false,
form: this.createEmptyForm(),
// ==================== 详情 ====================
detailDialogVisible: false,
detailLoading: false,
detailForm: {}
}
},
mounted() {
this.fetchList()
},
methods: {
createEmptyAddForm() {
/** 序号:按当前页数据索引生成跨页连续的序号 */
fmtXh(index) {
return (this.pageNum - 1) * this.pageSize + index + 1
},
createEmptyForm() {
return {
bsh: '',
gfmc: '',
xh: '',
xh: undefined,
zydm: '',
zymc: '',
zyfx: '',
xnz: '',
xqs: '',
xqs: undefined,
pxcc: '',
pxlx: '',
jxgljgbh: '',
bz: '',
pxlx2: '',
jxgljgbh: '',
xylb: '',
zdyfl: '',
jsonzd: '',
zgzy: ''
zgzy: '',
bz: ''
}
},
// ==================== 查询列表 ====================
// TODO: 后端接口未提供,暂用模拟数据;接口就绪后替换为 getDisciplineList
fetchList() {
this.loading = true
setTimeout(() => {
let records = mockTableData.slice()
if (this.searchForm.zymc.trim()) {
records = records.filter((item) => (item.zymc || '').includes(this.searchForm.zymc.trim()))
const params = {
pageNum: this.pageNum,
pageSize: this.pageSize
}
// 状态:勾选「停用」时仅查已停用(ty=1),未勾选则查询全部
if (this.searchForm.ty) {
params.ty = 1
}
// 其余文本条件非空时才传
;['zymc', 'zydm', 'zyfx', 'xnz', 'pxlx2'].forEach(key => {
const value = this.searchForm[key]
if (value !== '' && value !== null && value !== undefined) {
params[key] = String(value).trim()
}
if (this.searchForm.zydm.trim()) {
records = records.filter((item) => (item.zydm || '').includes(this.searchForm.zydm.trim()))
}
if (this.searchForm.zyfx.trim()) {
records = records.filter((item) => (item.zyfx || '').includes(this.searchForm.zyfx.trim()))
}
if (this.searchForm.xnz.trim()) {
records = records.filter((item) => String(item.xnz || '').includes(this.searchForm.xnz.trim()))
}
if (this.searchForm.ty) {
records = records.filter((item) => item.ty === 1)
}
this.allData = records
this.total = records.length
this.applyPage()
})
listDiscipline(params).then(response => {
const data = response.data || {}
this.tableData = data.records || []
this.total = data.total || 0
this.loading = false
}, 200)
},
applyPage() {
const start = (this.pageNum - 1) * this.pageSize
this.tableData = this.allData.slice(start, start + this.pageSize)
}).catch(() => {
this.tableData = []
this.total = 0
this.loading = false
})
},
handleSearch() {
this.pageNum = 1
this.fetchList()
this.$message.success('查询完成(前端模拟)')
},
handleReset() {
@@ -323,47 +367,127 @@ export default {
this.fetchList()
},
// ==================== 新增/编辑 ====================
handleAdd() {
this.isAdd = true
this.dialogTitle = '新增学科专业'
this.form = this.createEmptyForm()
this.formDialogVisible = true
this.$nextTick(() => {
this.$refs.formRef && this.$refs.formRef.clearValidate()
})
},
handleEdit(row) {
this.isAdd = false
this.dialogTitle = '编辑学科专业'
this.form = this.createEmptyForm()
this.formDialogVisible = true
this.$nextTick(() => {
this.$refs.formRef && this.$refs.formRef.clearValidate()
})
getDiscipline(row.bsh).then(response => {
const data = response.data || {}
this.form = {
bsh: data.bsh || '',
gfmc: data.gfmc || '',
xh: data.xh !== null && data.xh !== undefined ? data.xh : undefined,
zydm: data.zydm || '',
zymc: data.zymc || '',
zyfx: data.zyfx || '',
xnz: data.xnz || '',
xqs: data.xqs !== null && data.xqs !== undefined ? data.xqs : undefined,
pxcc: data.pxcc || '',
pxlx: data.pxlx || '',
pxlx2: data.pxlx2 || '',
jxgljgbh: data.jxgljgbh || '',
xylb: data.xylb || '',
zdyfl: data.zdyfl || '',
jsonzd: data.jsonzd || '',
zgzy: data.zgzy || '',
bz: data.bz || ''
}
}).catch(() => {})
},
buildPayload() {
const f = this.form
const payload = {
bsh: f.bsh || undefined,
gfmc: f.gfmc,
zydm: f.zydm,
zymc: f.zymc,
zyfx: f.zyfx,
xnz: f.xnz,
pxcc: f.pxcc,
pxlx: f.pxlx,
pxlx2: f.pxlx2,
jxgljgbh: f.jxgljgbh,
xylb: f.xylb,
zdyfl: f.zdyfl,
zgzy: f.zgzy,
jsonzd: f.jsonzd,
bz: f.bz
}
// 序号、学期数为数值字段,转为数字后再提交
if (f.xh !== '' && f.xh !== null && f.xh !== undefined) {
payload.xh = Number(f.xh)
}
if (f.xqs !== '' && f.xqs !== null && f.xqs !== undefined) {
payload.xqs = Number(f.xqs)
}
return payload
},
handleFormSubmit() {
this.formSaving = true
const payload = this.buildPayload()
const request = this.isAdd ? addDiscipline(payload) : updateDiscipline(payload)
request.then(() => {
this.$message.success(this.isAdd ? '新增成功' : '修改成功')
this.formDialogVisible = false
this.fetchList()
}).catch(() => {
}).finally(() => {
this.formSaving = false
})
},
// ==================== 停用/启用 ====================
// TODO: 后端接口未提供,停用/启用为前端模拟;接口就绪后替换为 toggleDiscipline
handleDisable(row) {
this.$confirm(`确定要停用「${row.zymc || row.zydm || '该专业'}」吗?`, '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
row.ty = 1
this.$message.success('停用成功(前端模拟)')
this.fetchList()
}).catch(() => {})
},
handleEnable(row) {
this.$confirm(`确定要启用「${row.zymc || row.zydm || '该专业'}」吗?`, '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
return disableDiscipline(row.bsh)
}).then(() => {
row.ty = 0
this.$message.success('启用成功(前端模拟)')
this.$message.success('停用成功')
this.fetchList()
}).catch(() => {})
},
// ==================== 新增学科专业 ====================
handleAdd() {
this.addForm = this.createEmptyAddForm()
this.addDialogVisible = true
// 后端暂未提供启用接口,仅作提示
handleEnable(row) {
this.$message.info('后端暂未提供该接口')
},
// TODO: 后端接口未提供,新增为前端模拟;接口就绪后替换为 addDiscipline
handleAddConfirm() {
this.addSaving = true
setTimeout(() => {
this.$message.success('新增学科专业成功(前端模拟)')
this.addDialogVisible = false
this.fetchList()
this.addSaving = false
}, 300)
// ==================== 详情 ====================
// 数值字段空值显示为 '-'(保留 0)
fmtValue(val) {
return val === null || val === undefined || val === '' ? '-' : val
},
handleDetail(row) {
this.detailDialogVisible = true
this.detailLoading = true
this.detailForm = {}
getDiscipline(row.bsh).then(response => {
this.detailForm = response.data || {}
this.detailLoading = false
}).catch(() => {
this.detailLoading = false
})
}
}
}
@@ -2,7 +2,7 @@
<div class="app-container major-page">
<!-- ==================== 1. 查询条件 ==================== -->
<el-card shadow="never" class="search-card">
<el-form ref="searchFormRef" :model="searchForm" label-width="90px" class="search-form">
<el-form ref="searchFormRef" :model="searchForm" label-width="140px" class="search-form">
<el-row :gutter="24">
<el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="8">
<el-form-item label="专业名称">
@@ -72,7 +72,9 @@
</div>
</div>
<el-table v-loading="loading" :data="tableData" stripe border highlight-current-row>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column label="序号" width="60" align="center">
<template slot-scope="scope">{{ fmtXh(scope.$index) }}</template>
</el-table-column>
<el-table-column prop="zydh" label="专业代号" width="120" show-overflow-tooltip />
<el-table-column prop="zymc" label="专业名称" min-width="140" show-overflow-tooltip />
<el-table-column prop="zyfx" label="专业方向" min-width="120" show-overflow-tooltip />
@@ -115,8 +115,8 @@ export default {
totalWeeks: 0,
dateRangeText: '',
weeks: [],
// 显示控制:默认勾选显示日期;节次全部展示
showDate: true,
// 显示控制:默认不勾选显示日期;节次全部展示
showDate: false,
// 事件存储:key = cellKey -> event
events: {},
loading: false
@@ -1,7 +1,7 @@
<template>
<div class="app-container teach-semester">
<!-- 工具栏 -->
<el-row :gutter="10" class="mb8">
<el-row :gutter="10" class="semester-toolbar">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd">新添</el-button>
</el-col>
@@ -17,62 +17,75 @@
</el-row>
<!-- 数据表格 -->
<el-table
v-loading="loading"
:data="semesterList"
border
:height="tableHeight"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" align="center" width="50" />
<el-table-column label="学期名称" align="center" min-width="190">
<template slot-scope="scope">
<span>{{ getSemesterName(scope.row.nd) }}</span>
</template>
</el-table-column>
<el-table-column label="当前" align="center" width="70">
<template slot-scope="scope">
<el-tag v-if="scope.row.dqxq" type="success" size="mini">当前</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="开学日期" align="center" prop="kxrq" min-width="130">
<template slot-scope="scope">
<span>{{ formatDate(scope.row.kxrq) }}</span>
</template>
</el-table-column>
<el-table-column label="结束日期" align="center" prop="jsrq" min-width="130">
<template slot-scope="scope">
<span>{{ formatDate(scope.row.jsrq) }}</span>
</template>
</el-table-column>
<el-table-column label="周数" align="center" prop="sdzs" width="70" />
<el-table-column label="调课不审批" align="center" width="100">
<template slot-scope="scope">
<el-tag :type="scope.row.tkbsp ? 'primary' : 'info'" size="mini">{{ scope.row.tkbsp ? '是' : '否' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="禁止调课" align="center" width="90">
<template slot-scope="scope">
<el-tag :type="scope.row.jztk ? 'danger' : 'info'" size="mini">{{ scope.row.jztk ? '是' : '否' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="终结成绩定及格" align="center" width="120">
<template slot-scope="scope">
<el-tag :type="scope.row.zjcjdjg ? 'primary' : 'info'" size="mini">{{ scope.row.zjcjdjg ? '是' : '否' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="课时系数方案" align="center" min-width="120">
<template slot-scope="scope">
<span>{{ getCoefficientLabel(scope.row.ksxsfabh) }}</span>
</template>
</el-table-column>
<el-table-column label="锁定时长" align="center" min-width="100">
<template slot-scope="scope">
<span>{{ scope.row.sdjldw || '-' }}</span>
</template>
</el-table-column>
</el-table>
<div class="semester-table-wrap">
<el-table
v-loading="loading"
:data="semesterList"
border
stripe
:height="tableHeight"
:row-class-name="tableRowClassName"
:header-cell-style="{ background: '#F4F8F5', color: '#303133', fontWeight: 600 }"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" align="center" width="50" />
<el-table-column label="学期名称" align="center" min-width="190">
<template slot-scope="scope">
<span class="semester-name">
<i v-if="scope.row.dqxq" class="semester-name-icon el-icon-star-on"></i>
<span>{{ getSemesterName(scope.row.nd) }}</span>
</span>
</template>
</el-table-column>
<el-table-column label="当前" align="center" width="80">
<template slot-scope="scope">
<el-tag v-if="scope.row.dqxq" type="success" effect="dark" size="mini">当前学期</el-tag>
<span v-else class="text-muted">—</span>
</template>
</el-table-column>
<el-table-column label="开学日期" align="center" min-width="120">
<template slot-scope="scope">
<span class="date-cell"><i class="el-icon-date"></i>{{ formatDate(scope.row.kxrq) || '—' }}</span>
</template>
</el-table-column>
<el-table-column label="结束日期" align="center" min-width="120">
<template slot-scope="scope">
<span class="date-cell"><i class="el-icon-date"></i>{{ formatDate(scope.row.jsrq) || '—' }}</span>
</template>
</el-table-column>
<el-table-column label="周数" align="center" prop="sdzs" width="80">
<template slot-scope="scope">
<el-tag v-if="scope.row.sdzs !== undefined && scope.row.sdzs !== null" type="info" effect="plain" size="mini">{{ scope.row.sdzs }} 周</el-tag>
<span v-else class="text-muted">—</span>
</template>
</el-table-column>
<el-table-column label="调课不审批" align="center" width="116">
<template slot-scope="scope">
<span :class="boolCellClass(scope.row.tkbsp)">{{ scope.row.tkbsp ? '是' : '否' }}</span>
</template>
</el-table-column>
<el-table-column label="禁止调课" align="center" width="100">
<template slot-scope="scope">
<span :class="boolCellClass(scope.row.jztk)">{{ scope.row.jztk ? '是' : '否' }}</span>
</template>
</el-table-column>
<el-table-column label="终结成绩定及格" align="center" width="132">
<template slot-scope="scope">
<span :class="boolCellClass(scope.row.zjcjdjg)">{{ scope.row.zjcjdjg ? '是' : '否' }}</span>
</template>
</el-table-column>
<el-table-column label="课时系数方案" align="center" min-width="120">
<template slot-scope="scope">
<span>{{ getCoefficientLabel(scope.row.ksxsfabh) }}</span>
</template>
</el-table-column>
<el-table-column label="锁定时长" align="center" min-width="96">
<template slot-scope="scope">
<span class="lock-cell">{{ scope.row.sdjldw || '—' }}</span>
</template>
</el-table-column>
</el-table>
</div>
<pagination
v-show="total > 0"
@@ -335,6 +348,14 @@ export default {
this.loading = false
})
},
/** 当前学期行高亮类名 */
tableRowClassName({ row }) {
return row.dqxq ? 'current-semester-row' : ''
},
/** 布尔开关单元格样式:是=实心绿点,否=灰色横线标记 */
boolCellClass(val) {
return val ? 'bool-cell is-true' : 'bool-cell is-false'
},
/** 多选变化 */
handleSelectionChange(selection) {
this.ids = selection.map(item => item.nd)
@@ -491,8 +512,146 @@ export default {
<style scoped lang="scss">
.teach-semester {
.mb8 {
margin-bottom: 8px;
/* 页面标题区 */
.semester-header {
display: flex;
align-items: center;
gap: 12px;
padding-bottom: 14px;
margin-bottom: 14px;
border-bottom: 1px solid #ebeef5;
}
.semester-title {
display: flex;
align-items: center;
gap: 8px;
font-size: 18px;
font-weight: 600;
color: #303133;
white-space: nowrap;
}
.title-dot {
width: 5px;
height: 18px;
border-radius: 3px;
background: var(--color, #00875A);
}
.semester-subtitle {
font-size: 13px;
color: #909399;
line-height: 1.5;
padding-top: 1px;
}
/* 工具栏 */
.semester-toolbar {
margin-bottom: 14px;
}
/* 表格卡片容器 */
.semester-table-wrap {
background: #fff;
border: 1px solid #ebeef5;
border-radius: 8px;
padding: 8px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
}
/* 表头由 header-cell-style 设置底色,这里兜底 */
::v-deep .el-table__header th {
font-weight: 600;
}
/* 当前学期行高亮:浅绿底 + 左侧加深强调 */
::v-deep .el-table .current-semester-row td {
background: #F2FAF5;
}
::v-deep .el-table .current-semester-row:hover > td {
background: #EAF6EF;
}
::v-deep .el-table .current-semester-row td:first-child {
box-shadow: inset 3px 0 0 #00875A;
}
/* 学期名称 + 当前星标 */
.semester-name {
display: inline-flex;
align-items: center;
gap: 4px;
font-weight: 500;
color: #303133;
}
.semester-name-icon {
color: #00875A;
font-size: 14px;
}
/* 日期单元格 */
.date-cell {
display: inline-flex;
align-items: center;
gap: 4px;
color: #606266;
font-variant-numeric: tabular-nums;
i {
color: #a0a6ad;
font-size: 13px;
}
}
/* 锁定时长 */
.lock-cell {
background: #F4F6F8;
border: 1px solid #EBEEF5;
border-radius: 4px;
padding: 1px 8px;
font-size: 12px;
color: #606266;
}
/* 布尔开关:实心圆点 + 是/否 */
.bool-cell {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 13px;
&::before {
content: '';
display: inline-block;
width: 9px;
height: 9px;
border-radius: 50%;
}
&.is-true {
color: #00875A;
font-weight: 600;
&::before {
background: #00875A;
}
}
&.is-false {
color: #C0C4CC;
&::before {
border: 1px solid #CDD1D6;
background: transparent;
}
}
}
.text-muted {
color: #C0C4CC;
}
/* 弹窗内表单:防止 label 文字换行、字段拥挤换行 */
@@ -2,64 +2,28 @@
<div class="app-container syllabus-page">
<!-- 查询条件 -->
<el-card shadow="never" class="search-card">
<el-form :model="searchForm" label-width="120px" class="search-form">
<el-form :model="searchForm" label-width="100px" class="search-form" @submit.native.prevent>
<el-row :gutter="32">
<!-- 左栏 -->
<el-col :xs="24" :md="12">
<el-form-item label="专业名称">
<el-input v-model="searchForm.zymc" placeholder="请输入专业名称" clearable />
</el-form-item>
<el-form-item label="专业方向">
<el-input v-model="searchForm.zyfx" placeholder="请输入专业方向" clearable />
</el-form-item>
<el-form-item>
<template slot="label"><el-checkbox v-model="searchForm.pxlxEnabled">培训类型</el-checkbox></template>
<el-select v-model="searchForm.pxlx" placeholder="请选择培训类型" clearable class="w-full" :disabled="!searchForm.pxlxEnabled">
<el-option v-for="item in pxlxOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item>
<template slot="label"><el-checkbox v-model="searchForm.pxccEnabled">培训层次</el-checkbox></template>
<el-select v-model="searchForm.pxcc" placeholder="请选择培训层次" clearable class="w-full" :disabled="!searchForm.pxccEnabled">
<el-option v-for="item in pxccOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item label="自定义分类">
<el-input v-model="searchForm.zdyfl" placeholder="请输入自定义分类" clearable />
<el-col :xs="24" :md="8">
<el-form-item label="专业代号">
<el-input v-model="searchForm.zydh" placeholder="请输入专业代号" clearable />
</el-form-item>
</el-col>
<!-- 右栏 -->
<el-col :xs="24" :md="12">
<el-form-item label="专业代码">
<el-input v-model="searchForm.zydm" placeholder="请输入专业代码" clearable />
</el-form-item>
<el-form-item label="学年制">
<el-input v-model="searchForm.xnz" placeholder="请输入学年制" clearable />
</el-form-item>
<el-form-item label="培训类型2">
<el-select v-model="searchForm.pxlx2" placeholder="请选择培训类型2" clearable class="w-full">
<el-option v-for="item in pxlx2Options" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item label="启用状态">
<el-checkbox v-model="searchForm.ty">停用</el-checkbox>
</el-form-item>
<el-form-item label="大纲版本">
<el-input v-model="searchForm.dgbb" placeholder="请输入大纲版本" clearable />
</el-form-item>
<el-form-item>
<template slot="label"><el-checkbox v-model="searchForm.gljgEnabled">管理机构</el-checkbox></template>
<el-select v-model="searchForm.gljg" placeholder="请选择管理机构" clearable class="w-full" :disabled="!searchForm.gljgEnabled">
<el-option v-for="item in gljgOptions" :key="item" :label="item" :value="item" />
<el-col :xs="24" :md="8">
<el-form-item label="停用标识">
<el-select v-model="searchForm.ty" placeholder="全部" clearable class="w-full">
<el-option :value="0" label="正常" />
<el-option :value="1" label="停用" />
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :md="8">
<div class="search-actions">
<el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
<el-button icon="el-icon-refresh" @click="handleReset">重置</el-button>
</div>
</el-col>
</el-row>
<div class="search-footer">
<div class="notice">注意:勾选"选择框"表示启用该项对应的查询条件。</div>
<el-button type="primary" @click="handleQuery">查询</el-button>
</div>
</el-form>
</el-card>
@@ -67,79 +31,393 @@
<el-card shadow="never" class="table-card">
<div class="list-header">
<div class="list-title">教学大纲列表</div>
<el-button type="primary" @click="handleDownload">下载</el-button>
<div class="list-actions">
<el-button type="primary" icon="el-icon-plus" @click="handleAdd">新增</el-button>
<el-button type="danger" icon="el-icon-delete" :disabled="!selection.length" @click="handleBatchDelete">删除所选</el-button>
<el-button icon="el-icon-download" @click="handleDownload">下载</el-button>
</div>
</div>
<el-table :data="tableData" border stripe style="width: 100%">
<el-table-column prop="jxgljg" label="教学管理机构" min-width="140" />
<el-table-column prop="xkzyxx" label="学科专业信息" min-width="140" />
<el-table-column prop="jxdgxx" label="教学大纲信息" min-width="200" />
<el-table-column prop="fl" label="分类" width="100" align="center" />
<el-table-column prop="xylb" label="学员类别" width="100" align="center" />
<el-table-column prop="xnz" label="学年制" width="80" align="center" />
<el-table-column prop="xsfb" label="学时分布" min-width="140" align="center" />
<el-table-column prop="zy" label="停用" width="80" align="center" />
<el-table v-loading="loading" :data="pagedData" border stripe highlight-current-row
@selection-change="handleSelectionChange">
<el-table-column type="selection" width="50" align="center" />
<el-table-column prop="bh" label="编号" width="170" show-overflow-tooltip align="center" />
<el-table-column prop="zydh" label="专业代号" width="150" align="center" />
<el-table-column prop="kbh" label="课编号" width="110" show-overflow-tooltip align="center" />
<el-table-column prop="jc" label="简称" width="100" show-overflow-tooltip align="center" />
<el-table-column prop="klx" label="课类型" width="80" align="center" />
<el-table-column prop="xqdc" label="学期第次" width="80" align="center" />
<el-table-column prop="xs" label="学时" width="70" align="center" />
<el-table-column prop="xf" label="学分" width="70" align="center" />
<el-table-column prop="llxs" label="理论学时" width="80" align="center" />
<el-table-column prop="sjxs" label="实践学时" width="80" align="center" />
<el-table-column prop="zks" label="周课时" width="70" align="center" />
<el-table-column prop="ksks" label="考试课时" width="80" align="center" />
<el-table-column label="大纲课程" width="90" align="center">
<template slot-scope="scope">{{ fmtYesNo(scope.row.dgkc) }}</template>
</el-table-column>
<el-table-column label="停用" width="80" align="center">
<template slot-scope="scope">
<el-tag :type="Number(scope.row.ty) === 1 ? 'danger' : 'success'" size="mini">
{{ Number(scope.row.ty) === 1 ? '停用' : '正常' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="启用时间" width="150" align="center">
<template slot-scope="scope">{{ fmtDateTime(scope.row.qysj) }}</template>
</el-table-column>
<el-table-column label="停用时间" width="150" align="center">
<template slot-scope="scope">{{ fmtDateTime(scope.row.tysj) }}</template>
</el-table-column>
<el-table-column label="操作" width="140" align="center" fixed="right">
<template slot-scope="scope">
<el-button type="text" size="mini" icon="el-icon-edit" @click="handleEdit(scope.row)">编辑</el-button>
<el-button type="text" size="mini" icon="el-icon-delete" class="danger-text-btn" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
class="pagination"
background
layout="total, sizes, prev, pager, next"
:current-page="pageNum"
:page-size="pageSize"
:total="total"
:page-sizes="[10, 20, 50, 100]"
@size-change="handleSizeChange"
@current-change="handlePageChange"
/>
</el-card>
<!-- 新增/编辑弹窗 -->
<el-dialog :title="dialog.title" :visible.sync="dialog.visible" width="720px" append-to-body
:close-on-click-modal="false">
<el-form ref="syllabusForm" :model="dialog.form" :rules="rules" label-width="130px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="专业代号" prop="zydh">
<el-input v-model="dialog.form.zydh" placeholder="请输入专业代号" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="课编号" prop="kbh">
<el-input v-model="dialog.form.kbh" placeholder="请输入课编号" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="学期第次" prop="xqdc">
<el-input-number v-model="dialog.form.xqdc" :min="1" :max="20" controls-position="right" class="w-full" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="课类型" prop="klx">
<el-input v-model="dialog.form.klx" placeholder="请输入课类型" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="学时" prop="xs">
<el-input-number v-model="dialog.form.xs" :min="0" controls-position="right" class="w-full" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="停用" prop="ty">
<el-select v-model="dialog.form.ty" class="w-full">
<el-option :value="0" label="正常" />
<el-option :value="1" label="停用" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="简称">
<el-input v-model="dialog.form.jc" placeholder="请输入课程简称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="学分">
<el-input-number v-model="dialog.form.xf" :min="0" :step="0.5" :precision="1" controls-position="right" class="w-full" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="课程定位">
<el-input v-model="dialog.form.kcdw" placeholder="请输入课程定位" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="模块">
<el-input v-model="dialog.form.mk" placeholder="请输入模块" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="成绩分制">
<el-input v-model="dialog.form.cjfz" placeholder="请输入成绩分制" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="教研室代号">
<el-input v-model="dialog.form.jysdh" placeholder="请输入教研室代号" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="考试课时">
<el-input-number v-model="dialog.form.ksks" :min="0" controls-position="right" class="w-full" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="理论学时">
<el-input-number v-model="dialog.form.llxs" :min="0" controls-position="right" class="w-full" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="实践学时">
<el-input-number v-model="dialog.form.sjxs" :min="0" controls-position="right" class="w-full" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="周课时">
<el-input-number v-model="dialog.form.zks" :min="0" controls-position="right" class="w-full" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="不计入平均分">
<el-radio-group v-model="dialog.form.bjrxypjf">
<el-radio :label="0">否</el-radio>
<el-radio :label="1">是</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="考试课时不显示">
<el-radio-group v-model="dialog.form.ksksbxs">
<el-radio :label="0">否</el-radio>
<el-radio :label="1">是</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="大纲课程">
<el-radio-group v-model="dialog.form.dgkc">
<el-radio :label="0">否</el-radio>
<el-radio :label="1">是</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialog.visible = false">取 消</el-button>
<el-button type="primary" :loading="dialog.submitting" @click="submitDialog">确 定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
// 模拟列表数据(后端接口未提供,接口就绪后可删除并改为接口加载)
const mockTableData = [
{ jxgljg: '教务处', xkzyxx: '通信工程', jxdgxx: '通信工程导论教学大纲', fl: '专业基础', xylb: '军官', xnz: '4', xsfb: '理论48/实践16', zy: '教学' },
{ jxgljg: '教务处', xkzyxx: '信息工程', jxdgxx: '信息工程基础教学大纲', fl: '专业基础', xylb: '军官', xnz: '4', xsfb: '理论64/实践32', zy: '教学' }
]
import {
addSyllabus,
deleteSyllabus,
batchDeleteSyllabus,
updateSyllabus,
listSyllabus,
listSyllabusByZydhAndTy
} from '@/api/teachBusiness/syllabus'
export default {
name: 'SyllabusIndex',
data() {
return {
// ==================== 查询表单 ====================
loading: false,
searchForm: {
zymc: '',
zydm: '',
zyfx: '',
xnz: '',
pxlx: '军官基础教育', pxlxEnabled: false,
pxlx2: '其他',
pxcc: '无', pxccEnabled: false,
ty: false,
zdyfl: '',
dgbb: '',
gljg: '教务处', gljgEnabled: false
zydh: '',
ty: ''
},
// ==================== 下拉选项(字典数据,接口就绪后可由后端字典加载) ====================
pxlxOptions: ['军官基础教育', '士兵职业教育', '研究生教育', '其他'],
pxlx2Options: ['军官基础教育', '其他', '任职培训', '研究生教育'],
pxccOptions: ['无', '本科', '硕士', '博士'],
gljgOptions: ['教务处', '教研室1', '教研室2', '学员队'],
// ==================== 表格数据 ====================
tableData: []
tableData: [],
total: 0,
pageNum: 1,
pageSize: 20,
selection: [],
dialog: {
visible: false,
title: '',
submitting: false,
form: this.createEmptyForm()
},
rules: {
zydh: [{ required: true, message: '请输入专业代号', trigger: 'blur' }],
kbh: [{ required: true, message: '请输入课编号', trigger: 'blur' }],
xqdc: [{ required: true, message: '请输入学期第次', trigger: 'blur' }],
klx: [{ required: true, message: '请输入课类型', trigger: 'blur' }],
xs: [{ required: true, message: '请输入学时', trigger: 'blur' }],
ty: [{ required: true, message: '请选择停用标识', trigger: 'change' }]
}
}
},
mounted() {
computed: {
pagedData() {
const start = (this.pageNum - 1) * this.pageSize
return this.tableData.slice(start, start + this.pageSize)
}
},
created() {
this.fetchList()
},
methods: {
// ==================== 列表加载 ====================
// TODO: 后端接口未提供,暂用模拟数据;接口就绪后替换为 getSyllabusList
/* ---------- 列表加载 ---------- */
fetchList() {
this.tableData = mockTableData.slice()
this.loading = true
const { zydh, ty } = this.searchForm
// 专业代号与停用标识同时给出时走后端 /listByZydhAndTy;否则查全部后本地过滤
const useApiQuery = zydh && ty !== '' && ty !== null && ty !== undefined
const req = useApiQuery
? listSyllabusByZydhAndTy(zydh, ty)
: listSyllabus()
req.then(response => {
let list = response.data || []
if (!useApiQuery) {
if (zydh) list = list.filter(i => i.zydh && i.zydh.indexOf(zydh) !== -1)
if (ty !== '' && ty !== null && ty !== undefined) {
list = list.filter(i => Number(i.ty) === Number(ty))
}
}
this.tableData = list
this.total = list.length
this.pageNum = 1
}).catch(() => {
this.tableData = []
this.total = 0
}).finally(() => {
this.loading = false
})
},
// ==================== 查询 ====================
// TODO: 后端接口未提供,查询为前端模拟;接口就绪后替换为后端查询
handleQuery() {
this.fetchList()
this.$message.success('查询教学大纲(前端模拟)')
},
handleReset() {
this.searchForm = { zydh: '', ty: '' }
this.fetchList()
},
handleSelectionChange(val) {
this.selection = val
},
// ==================== 下载 ====================
// TODO: 后端接口未提供,下载为前端模拟;接口就绪后替换为 downloadSyllabusList
/* ---------- 新增 / 编辑 ---------- */
handleAdd() {
this.dialog.title = '新增教学大纲'
this.dialog.form = this.createEmptyForm()
this.dialog.visible = true
this.$nextTick(() => {
if (this.$refs.syllabusForm) this.$refs.syllabusForm.clearValidate()
})
},
handleEdit(row) {
this.dialog.title = '编辑教学大纲'
this.dialog.form = Object.assign({}, this.createEmptyForm(), row)
this.dialog.visible = true
this.$nextTick(() => {
if (this.$refs.syllabusForm) this.$refs.syllabusForm.clearValidate()
})
},
submitDialog() {
this.$refs.syllabusForm.validate(valid => {
if (!valid) return
this.dialog.submitting = true
const payload = this.cleanPayload(this.dialog.form)
const isEdit = !!payload.bh
const req = isEdit ? updateSyllabus(payload) : addSyllabus(payload)
req.then(() => {
this.$message.success(isEdit ? '修改成功' : '新增成功')
this.dialog.visible = false
this.fetchList()
}).finally(() => {
this.dialog.submitting = false
})
})
},
/* ---------- 删除 / 批量删除 ---------- */
handleDelete(row) {
this.$confirm('删除后该条记录将置为「停用」,是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
return deleteSyllabus(row.bh)
}).then(() => {
this.$message.success('删除成功')
this.fetchList()
}).catch(() => {})
},
handleBatchDelete() {
if (!this.selection.length) {
this.$message.warning('请先勾选要删除的记录')
return
}
const bhList = this.selection.map(row => row.bh)
this.$confirm('确定删除所选 ' + bhList.length + ' 条记录吗?(将置为「停用」)', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
return batchDeleteSyllabus(bhList)
}).then(() => {
this.$message.success('批量删除成功')
this.fetchList()
}).catch(() => {})
},
/* ---------- 下载(无后端接口) ---------- */
handleDownload() {
this.$message.success('下载教学大纲列表(前端模拟)')
this.$message.warning('后端暂未提供该接口')
},
/* ---------- 分页 ---------- */
handleSizeChange(size) {
this.pageSize = size
this.pageNum = 1
},
handlePageChange(page) {
this.pageNum = page
},
/* ---------- 工具 ---------- */
createEmptyForm() {
return {
bh: '',
zydh: '',
kbh: '',
xqdc: 1,
klx: '',
xs: 0,
ty: 0,
jc: '',
xf: null,
kcdw: '',
ksks: 0,
mk: '',
cjfz: '',
bjrxypjf: 0,
llxs: 0,
sjxs: 0,
zks: 0,
ksksbxs: 0,
dgkc: 0,
jysdh: ''
}
},
/** 移除空值(''/null/undefined),保留 0 等有效值 */
cleanPayload(obj) {
const payload = {}
Object.keys(obj).forEach(key => {
const value = obj[key]
if (value !== '' && value !== null && value !== undefined) {
payload[key] = value
}
})
return payload
},
fmtYesNo(val) {
return Number(val) === 1 ? '是' : '否'
},
fmtDateTime(val) {
return (val || '').substring(0, 16) || '-'
}
}
}
@@ -159,23 +437,8 @@ export default {
width: 100%;
}
.el-form-item__label {
text-align: left;
justify-content: flex-start;
}
.search-footer {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 8px;
flex-wrap: wrap;
gap: 12px;
.notice {
font-size: 12px;
color: #f56c6c;
}
.search-actions {
padding-top: 4px;
}
}
}
@@ -193,6 +456,19 @@ export default {
color: #303133;
}
}
.pagination {
margin-top: 16px;
text-align: right;
}
}
}
.danger-text-btn {
color: #f56c6c;
&:hover {
color: #f78989;
}
}
</style>
@@ -1,282 +1,246 @@
<template>
<div class="app-container teaching-task-page">
<!-- 页面标题 -->
<div class="page-title">{{ pageTitle }}</div>
<!-- ==================== 页面标题 ==================== -->
<div class="page-title">教学任务列表</div>
<!-- 查询条件 -->
<!-- ==================== 1. 查询条件区域 ==================== -->
<el-card shadow="never" class="search-card">
<el-form :model="searchForm" label-width="110px" class="search-form">
<el-row :gutter="32">
<!-- 左栏 -->
<el-col :xs="24" :md="12">
<el-form-item label="授课教员">
<el-input v-model="searchForm.skjy" placeholder="请输入授课教员" clearable />
<el-form :model="searchForm" label-width="80px" class="search-form">
<el-row :gutter="24">
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="任务名称">
<el-input
v-model="searchForm.rwmc"
placeholder="请输入任务名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="可选班次年级">
<el-input v-model="searchForm.kxbcnj" placeholder="请输入可选班次年级" clearable />
</el-form-item>
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.fzkztEnabled">分组课状态</el-checkbox>
</template>
<el-select v-model="searchForm.fzkzt" placeholder="请选择分组课状态" class="w-full"
:disabled="!searchForm.fzkztEnabled">
<el-option v-for="item in groupStatusOptions" :key="item" :label="item" :value="item" />
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="年度">
<el-select
v-model="searchForm.nd"
placeholder="请选择年度"
clearable
style="width: 100%"
>
<el-option v-for="y in yearOptions" :key="y" :label="y" :value="y" />
</el-select>
</el-form-item>
</el-col>
<!-- 右栏 -->
<el-col :xs="24" :md="12">
<el-form-item label="课程科目名称">
<el-input v-model="searchForm.kckmmc" placeholder="请输入课程科目名称" clearable />
</el-form-item>
<el-form-item label="分组班名称">
<el-input v-model="searchForm.fzbmc" placeholder="请输入分组班名称" clearable />
</el-form-item>
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.pxccEnabled">培训层次</el-checkbox>
</template>
<el-select v-model="searchForm.pxcc" placeholder="请选择培训层次" class="w-full"
:disabled="!searchForm.pxccEnabled">
<el-option v-for="item in trainLevelOptions" :key="item" :label="item" :value="item" />
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="状态">
<el-select
v-model="searchForm.zt"
placeholder="请选择状态"
clearable
style="width: 100%"
>
<el-option
v-for="opt in statusOptions"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<div class="search-tip">
注意:勾选"选择框"表示启用该项对应的查询条件;文本输入框非空白表示启用对应的查询条件。
</div>
<div class="search-actions">
<el-button class="gray-btn" @click="handleSearch">查询</el-button>
<el-button class="gray-btn" @click="handleQuery">查询</el-button>
</div>
</el-form>
</el-card>
<!-- 下载按钮 -->
<div class="toolbar-right">
<el-button type="primary" @click="handleDownloadGroupCourseList">下载分组班列表</el-button>
<el-button type="primary" @click="handleDownloadWord">下载分组班学员名单Word</el-button>
<el-button type="primary" @click="handleDownloadExcel">下载分组班学员名单Excel</el-button>
</div>
<!-- 批量操作 -->
<el-card shadow="never" class="action-card">
<div class="action-groups">
<div class="action-group">
<el-button type="primary" plain @click="handleNewGroupCourse">新建分组课</el-button>
<el-button type="primary" plain @click="handleBatchOpen">所选班早开放报名</el-button>
<el-button type="primary" plain @click="handleBatchCancelOpen">所选班早取消开放报名</el-button>
</div>
<div class="action-group">
<el-button type="primary" plain @click="handleBatchStop">批量停止报名</el-button>
<el-button type="primary" plain @click="handleBatchReverseRange">所选班早根据学员反向确定班次范围</el-button>
</div>
</div>
</el-card>
<!-- 导入数据 -->
<el-card shadow="never" class="import-card">
<div class="import-toolbar">
<el-button type="primary" @click="handleDownloadTemplate">【分组课数据文件模板】下载</el-button>
<div class="file-input">
<input ref="fileInput" type="file" accept=".xls,.xlsx" style="display: none" @change="handleFileChange" />
<el-button type="primary" plain @click="handleChooseFile">选择文件</el-button>
<span class="file-name">{{ fileName }}</span>
</div>
<el-button type="primary" plain @click="handleUploadData">上传数据</el-button>
</div>
</el-card>
<!-- 数据表格 -->
<!-- ==================== 2. 数据表格 ==================== -->
<el-card shadow="never" class="table-card">
<el-table v-loading="loading" :data="tableData" stripe border highlight-current-row
@selection-change="handleSelectionChange">
<el-table v-loading="loading" :data="tableData" border stripe>
<template slot="empty">
<span>无数据!</span>
</template>
<el-table-column type="selection" width="50" align="center" />
<el-table-column type="index" label="序号" width="100" align="center" />
<el-table-column prop="kc" label="课程" width="100" show-overflow-tooltip />
<el-table-column prop="jc" label="教材" width="100" show-overflow-tooltip />
<el-table-column prop="ssjy" label="实施教员" width="150" align="center" />
<el-table-column prop="jxcd" label="教学场地" width="150" align="center" />
<el-table-column align="center">
<template slot="header">
<el-checkbox v-model="showOptionalClasses">显示可选队别班次</el-checkbox>
</template>
</el-table-column>
<el-table-column label="计划学时/运行学时/学分" width="130" align="center">
<template slot="header">
<div class="triple-header">
<span>计划学时</span>
<span>运行学时</span>
<span>学分</span>
</div>
</template>
<el-table-column type="index" label="序号" width="70" align="center" />
<el-table-column prop="bh" label="编号" width="200" align="center" show-overflow-tooltip />
<el-table-column prop="rwmc" label="任务名称" min-width="160" align="left" header-align="center" show-overflow-tooltip />
<el-table-column prop="nd" label="年度" width="90" align="center" />
<el-table-column label="状态" width="100" align="center">
<template slot-scope="{ row }">
<div class="triple-cell">
<span>{{ row.jhxs }}</span>
<span>{{ row.zxs }}</span>
<span>{{ row.xf }}</span>
</div>
<el-tag :type="row.zt === '发布' ? 'success' : 'info'" size="small">
{{ row.zt || '-' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="fbsj" label="发布时间" width="150" align="center" :formatter="fmtDateTime" />
<el-table-column prop="jssj" label="结束时间" width="150" align="center" :formatter="fmtDateTime" />
<el-table-column prop="cjsj" label="创建时间" width="150" align="center" :formatter="fmtDateTime" />
<el-table-column prop="jcxqscsj" label="教材需求生成时间" width="170" align="center" :formatter="fmtDateTime" />
<el-table-column label="操作" width="80" align="center" fixed="right">
<template slot-scope="{ row }">
<el-button type="text" size="small" @click="handleDetail(row)">详情</el-button>
</template>
</el-table-column>
<el-table-column prop="kkjkrq" label="开课结课日期" width="110" align="center" :formatter="formatDate" />
<el-table-column prop="bmrq" label="报名日期" width="120" align="center" :formatter="formatDate" />
<el-table-column prop="jhrs" label="计划人数" width="90" align="center" />
<el-table-column prop="yqsm" label="要求说明" width="150" show-overflow-tooltip />
</el-table>
<el-pagination
:current-page="pageNum"
:page-size="pageSize"
:total="total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
class="pagination"
@current-change="handlePageChange"
@size-change="handleSizeChange"
/>
</el-card>
<!-- ==================== 3. 详情对话框 ==================== -->
<el-dialog
:visible="detailVisible"
title="教学任务详情"
width="620px"
:close-on-click-modal="false"
@update:visible="val => detailVisible = val"
>
<el-descriptions v-if="detailData.bh" :column="2" border>
<el-descriptions-item label="编号">{{ detailData.bh || '-' }}</el-descriptions-item>
<el-descriptions-item label="任务名称">{{ detailData.rwmc || '-' }}</el-descriptions-item>
<el-descriptions-item label="年度">{{ fmtVal(detailData.nd) }}</el-descriptions-item>
<el-descriptions-item label="状态">{{ detailData.zt || '-' }}</el-descriptions-item>
<el-descriptions-item label="发布时间">{{ fmtVal(detailData.fbsj) }}</el-descriptions-item>
<el-descriptions-item label="结束时间">{{ fmtVal(detailData.jssj) }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ fmtVal(detailData.cjsj) }}</el-descriptions-item>
<el-descriptions-item label="教材需求生成时间">{{ fmtVal(detailData.jcxqscsj) }}</el-descriptions-item>
</el-descriptions>
<div v-else v-loading="detailLoading" class="detail-empty">加载中...</div>
<div slot="footer">
<el-button type="primary" @click="detailVisible = false">关闭</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
/**
* 教学任务列表(只读查询页)
* 对应菜单:teachBusiness/teachingTask(原「分组课列表」页,下载类功能后端无接口已移除)
* 仅提供 分页查询 list + 详情 get?bh=,不做增删改发(避免与教学任务计划管理页重复操作同一实体)
* 增删改发统一在 src/views/teachOffice/taskPlan/index.vue(教学任务计划管理)完成
*/
import { listTeachingTask, getTeachingTask } from '@/api/teachBusiness/teachingTask'
import { listAllSemester } from '@/api/teachBusiness/semester'
export default {
name: 'TeachingTask',
data() {
return {
pageTitle: '分组课列表',
// ==================== 查询表单 ====================
// ==================== 1. 查询条件 ====================
searchForm: {
skjy: '',
kckmmc: '',
kxbcnj: '',
fzbmc: '',
fzkzt: '拟制',
fzkztEnabled: false,
pxcc: '无',
pxccEnabled: false
rwmc: '',
nd: undefined,
zt: ''
},
groupStatusOptions: ['拟制', '已发布', '已结课'],
trainLevelOptions: ['无', '初级', '中级', '高级'],
// ==================== 批量操作 ====================
selectedRows: [],
// ==================== 表格数据 ====================
loading: false,
showOptionalClasses: false,
/** 模拟列表数据(后端接口未提供) */
tableData: [
{ xh: 1, kc: '高等数学', jc: '高等数学(上册)', ssjy: '张三', jxcd: '1-301', jhxs: 48, zxs: 46, xf: 3, kkjkrq: '2017-09-03', bmrq: '2017-08-20', jhrs: 40, yqsm: '按教学计划执行' },
{ xh: 2, kc: '军事英语', jc: '军事英语教程', ssjy: '李四', jxcd: '2-101', jhxs: 32, zxs: 30, xf: 2, kkjkrq: '2017-09-03', bmrq: '2017-08-22', jhrs: 36, yqsm: '含听力训练' },
{ xh: 3, kc: '导弹发射原理', jc: '导弹发射原理', ssjy: '王五', jxcd: '3-205', jhxs: 40, zxs: 40, xf: 2.5, kkjkrq: '2017-09-10', bmrq: '2017-08-25', jhrs: 42, yqsm: '实验课随堂进行' },
{ xh: 4, kc: '通信原理', jc: '通信原理', ssjy: '赵六', jxcd: '5-102', jhxs: 48, zxs: 44, xf: 3, kkjkrq: '2017-09-03', bmrq: '2017-08-28', jhrs: 38, yqsm: '' },
{ xh: 5, kc: '联合作战指挥', jc: '联合作战指挥', ssjy: '孙七', jxcd: '学术报告厅', jhxs: 40, zxs: 40, xf: 2.5, kkjkrq: '2017-09-17', bmrq: '2017-09-01', jhrs: 45, yqsm: '理论授课+想定作业' }
yearOptions: [],
statusOptions: [
{ label: '未发布', value: '未发布' },
{ label: '发布', value: '发布' }
],
// ==================== 导入 ====================
selectedFile: null
// ==================== 2. 表格数据 ====================
loading: false,
tableData: [],
pageNum: 1,
pageSize: 10,
total: 0,
// ==================== 3. 详情 ====================
detailVisible: false,
detailLoading: false,
detailData: {}
}
},
computed: {
fileName() {
return (this.selectedFile && this.selectedFile.name) || '未选择任何文件'
}
},
mounted() {
this.fetchList()
created() {
this.loadYearOptions().then(() => this.fetchList())
},
methods: {
/** 表格日期格式化 */
formatDate(row, column, cellValue) {
return cellValue ? String(cellValue).substring(0, 10) : ''
/* ---------- 年度下拉(数据来自 /semester/all,禁止硬编码) ---------- */
loadYearOptions() {
return listAllSemester().then(response => {
const list = response.data || []
const map = {}
list.forEach(item => {
if (item.nd !== null && item.nd !== undefined && item.nd !== '') map[item.nd] = item
})
const arr = Object.keys(map).sort((a, b) => String(b).localeCompare(String(a)))
this.yearOptions = arr
// 默认年度:优先当前学期(dqxq=true),否则取最新年度
const current = list.find(item => item.dqxq === true)
const defaultNd = (current && current.nd) || arr[0]
if (!this.searchForm.nd) this.searchForm.nd = defaultNd
return arr
}).catch(() => {
this.yearOptions = []
return []
})
},
// ==================== 列表加载 ====================
// TODO: 后端接口未提供,暂用模拟数据;接口就绪后替换为真实查询接口
/* ---------- 通用格式化 ---------- */
fmtDateTime(row, column, cellValue) {
if (cellValue === null || cellValue === undefined || cellValue === '') return '-'
return String(cellValue).replace('T', ' ').slice(0, 16)
},
fmtVal(val) {
if (val === null || val === undefined || val === '') return '-'
return String(val).replace('T', ' ').slice(0, 16)
},
/* ---------- 列表加载 ---------- */
fetchList() {
this.loading = true
setTimeout(() => {
const params = { pageNum: this.pageNum, pageSize: this.pageSize }
if (this.searchForm.rwmc && this.searchForm.rwmc.trim()) params.rwmc = this.searchForm.rwmc.trim()
if (this.searchForm.nd !== undefined && this.searchForm.nd !== null && this.searchForm.nd !== '') {
params.nd = this.searchForm.nd
}
if (this.searchForm.zt && this.searchForm.zt.trim()) params.zt = this.searchForm.zt.trim()
listTeachingTask(params).then(res => {
const data = (res && res.data) || {}
this.tableData = data.records || []
this.total = data.total || 0
this.loading = false
}, 200)
}).catch(() => {
this.tableData = []
this.total = 0
this.loading = false
})
},
// ==================== 查询 ====================
handleSearch() {
this.$message.success('查询分组课列表(前端模拟)')
handleQuery() {
this.pageNum = 1
this.fetchList()
},
// ==================== 批量操作 ====================
handleSelectionChange(rows) {
this.selectedRows = rows
handlePageChange(page) {
this.pageNum = page
this.fetchList()
},
handleNewGroupCourse() {
this.$message.success('新建分组课(前端模拟)')
handleSizeChange(size) {
this.pageSize = size
this.pageNum = 1
this.fetchList()
},
handleBatchOpen() {
if (this.selectedRows.length === 0) {
this.$message.warning('请先选择数据')
return
}
this.$message.success('所选批量开放报名(前端模拟)')
},
handleBatchCancelOpen() {
if (this.selectedRows.length === 0) {
this.$message.warning('请先选择数据')
return
}
this.$message.success('所选批量取消开放报名(前端模拟)')
},
handleBatchStop() {
if (this.selectedRows.length === 0) {
this.$message.warning('请先选择数据')
return
}
this.$message.success('批量停止报名(前端模拟)')
},
handleBatchReverseRange() {
if (this.selectedRows.length === 0) {
this.$message.warning('请先选择数据')
return
}
this.$message.success('所选批量根据学员反向确定班次范围(前端模拟)')
},
// ==================== 下载按钮 ====================
handleDownloadGroupCourseList() {
this.$message.success('下载分组课列表(前端模拟)')
},
handleDownloadWord() {
this.$message.success('下载分组学员名单Word(前端模拟)')
},
handleDownloadExcel() {
this.$message.success('下载分组学员名单Excel(前端模拟)')
},
// ==================== 导入 ====================
handleChooseFile() {
this.$refs.fileInput && this.$refs.fileInput.click()
},
handleFileChange(e) {
const input = e.target
if (input.files && input.files.length > 0) {
this.selectedFile = input.files[0]
}
},
handleDownloadTemplate() {
this.$message.success('下载分组课数据文件模板(前端模拟)')
},
handleUploadData() {
if (!this.selectedFile) {
this.$message.warning('请先选择文件')
return
}
this.$message.success('上传数据(前端模拟)')
/* ---------- 详情(只读,走真实 get?bh= 接口) ---------- */
handleDetail(row) {
this.detailVisible = true
this.detailLoading = true
this.detailData = {}
getTeachingTask(row.bh).then(res => {
this.detailData = (res && res.data) || {}
this.detailLoading = false
}).catch(() => {
this.detailLoading = false
})
}
}
}
@@ -287,99 +251,45 @@ export default {
padding: 20px;
}
.page-title {
text-align: center;
font-size: 20px;
font-weight: 600;
color: #303133;
margin-bottom: 20px;
}
.search-card {
margin-bottom: 16px;
.search-form {
.w-full {
width: 100%;
}
.teaching-task-page {
.page-title {
text-align: center;
font-size: 20px;
font-weight: 600;
color: #303133;
margin-bottom: 20px;
}
.search-tip {
font-size: 12px;
color: #f56c6c;
margin-top: 8px;
margin-bottom: 8px;
}
// ==================== 1. 查询条件区域 ====================
.search-card {
margin-bottom: 16px;
.search-actions {
display: flex;
justify-content: flex-end;
padding-top: 12px;
border-top: 1px solid #ebeef5;
}
}
.toolbar-right {
display: flex;
justify-content: flex-end;
gap: 12px;
margin-bottom: 16px;
}
.action-card {
margin-bottom: 16px;
.action-groups {
display: flex;
justify-content: flex-start;
gap: 12px;
flex-wrap: wrap;
.action-group {
display: flex;
flex-direction: row;
gap: 12px;
.el-button {
margin: 0;
.search-form {
.search-actions {
display: flex;
justify-content: flex-end;
padding-top: 12px;
border-top: 1px solid #ebeef5;
}
}
}
}
.import-card {
margin-bottom: 16px;
// ==================== 2. 数据表格 ====================
.table-card {
.pagination {
display: flex;
justify-content: flex-end;
margin-top: 12px;
}
}
.import-toolbar {
// ==================== 3. 详情 ====================
.detail-empty {
min-height: 120px;
display: flex;
align-items: center;
gap: 16px;
flex-wrap: wrap;
.file-input {
display: flex;
align-items: center;
gap: 8px;
.file-name {
font-size: 14px;
color: #606266;
min-width: 120px;
}
}
}
}
.table-card {
::v-deep(.el-table) {
width: 100%;
}
.triple-header,
.triple-cell {
display: flex;
flex-direction: column;
line-height: 1.4;
justify-content: center;
color: #909399;
}
}
@@ -11,58 +11,28 @@
<el-link type="primary" :underline="false" class="nav-link" @click="handleNextDay">后一天</el-link>
</div>
<!-- 批量操作栏 -->
<div class="toolbar-card">
<div class="toolbar">
<div class="toolbar-left">
<el-button type="primary" size="mini" :disabled="!todaySelected.length" @click="handleComing">批量设置备注信息</el-button>
<el-button type="primary" size="mini" :disabled="!todaySelected.length" @click="handleComing">批量变更时间</el-button>
<el-button type="primary" size="mini" :disabled="!todaySelected.length" @click="handleComing">批量平移时间</el-button>
<el-button type="primary" size="mini" :disabled="!todaySelected.length" @click="handleComing">批量变更授课教员</el-button>
<el-button type="primary" size="mini" :disabled="!todaySelected.length" @click="handleComing">批量变更授课场地</el-button>
<el-button type="danger" size="mini" :disabled="!todaySelected.length" @click="handleComing">批量删除</el-button>
</div>
<div class="toolbar-right">
<el-button type="primary" size="mini" @click="handleComing">自定义格式下载</el-button>
<el-button type="primary" size="mini" @click="handleComing">下载</el-button>
</div>
</div>
</div>
<!-- 今日表格 -->
<div class="table-card">
<el-table :data="todayList" v-loading="loading" stripe border @selection-change="todaySelected = $event">
<el-table-column type="selection" width="50" align="center" />
<el-table :data="todayList" v-loading="todayLoading" stripe border>
<el-table-column type="index" label="序号" width="55" align="center" />
<el-table-column label="上课时间" width="130" align="center">
<template slot-scope="scope">{{ formatTime(scope.row.jc) }}</template>
<el-table-column label="上课时间" width="200" align="center" show-overflow-tooltip>
<template slot-scope="scope">{{ fmtLessonTime(scope.row) }}</template>
</el-table-column>
<el-table-column prop="kcmc" label="课程" min-width="140" show-overflow-tooltip />
<el-table-column prop="zrdw" label="责任单位" width="110" align="center" />
<el-table-column prop="bc" label="班次" width="120" show-overflow-tooltip />
<el-table-column prop="skjy" label="教员" width="90" align="center" />
<el-table-column label="场地" width="140" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row.jxcd || '-' }}</template>
<el-table-column prop="kcmc" label="课程" min-width="150" show-overflow-tooltip header-align="center" />
<el-table-column prop="kcxh" label="课次序号" width="90" align="center" />
<el-table-column label="教学内容" min-width="180" show-overflow-tooltip header-align="center">
<template slot-scope="scope">{{ scope.row.jxnr || '-' }}</template>
</el-table-column>
<el-table-column label="教学内容方法" min-width="160" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row._jxnrff || '-' }}</template>
<el-table-column label="教学方法" width="130" align="center" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row.jxff || '-' }}</template>
</el-table-column>
<el-table-column prop="jybz" label="备注" width="100" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row.jybz || '-' }}</template>
</el-table-column>
<el-table-column label="计划变更明细" width="120" align="center">
<el-table-column label="填写状态" width="100" align="center">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="openDetail(scope.row)">变更明细</el-button>
</template>
</el-table-column>
<el-table-column label="操作" width="180" align="center">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="openRemark(scope.row)">编辑备注</el-button>
<el-button type="text" size="mini" class="danger-text" @click="handleComing">删除</el-button>
<el-tag :type="txztType(scope.row.txzt)" size="mini">{{ txztText(scope.row.txzt) }}</el-tag>
</template>
</el-table-column>
</el-table>
<el-empty v-show="!loading && !todayList.length" description="当日暂无教学计划数据" />
<el-empty v-show="!todayLoading && !todayList.length" description="当日暂无教学计划数据" />
</div>
</div>
</el-tab-pane>
@@ -77,58 +47,28 @@
<el-link type="primary" :underline="false" class="nav-link" @click="handleNextWeek">下一周</el-link>
</div>
<!-- 批量操作栏 -->
<div class="toolbar-card">
<div class="toolbar">
<div class="toolbar-left">
<el-button type="primary" size="mini" :disabled="!weekSelected.length" @click="handleComing">批量设置备注信息</el-button>
<el-button type="primary" size="mini" :disabled="!weekSelected.length" @click="handleComing">批量变更时间</el-button>
<el-button type="primary" size="mini" :disabled="!weekSelected.length" @click="handleComing">批量平移时间</el-button>
<el-button type="primary" size="mini" :disabled="!weekSelected.length" @click="handleComing">批量变更授课教员</el-button>
<el-button type="primary" size="mini" :disabled="!weekSelected.length" @click="handleComing">批量变更授课场地</el-button>
<el-button type="danger" size="mini" :disabled="!weekSelected.length" @click="handleComing">批量删除</el-button>
</div>
<div class="toolbar-right">
<el-button type="primary" size="mini" @click="handleComing">报批下载</el-button>
<el-button type="primary" size="mini" @click="handleComing">下载</el-button>
</div>
</div>
</div>
<!-- 本周表格 -->
<div class="table-card">
<el-table :data="weekList" v-loading="loading" stripe border @selection-change="weekSelected = $event">
<el-table-column type="selection" width="50" align="center" />
<el-table :data="weekList" v-loading="weekLoading" stripe border>
<el-table-column type="index" label="序号" width="55" align="center" />
<el-table-column label="上课时间" width="170" align="center" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row._sjsj || '-' }}</template>
<el-table-column label="上课时间" width="200" align="center" show-overflow-tooltip>
<template slot-scope="scope">{{ fmtLessonTime(scope.row) }}</template>
</el-table-column>
<el-table-column prop="kcmc" label="课程" min-width="130" show-overflow-tooltip />
<el-table-column prop="zrdw" label="责任单位" width="100" align="center" />
<el-table-column prop="bc" label="班次" width="130" show-overflow-tooltip />
<el-table-column prop="skjy" label="教员" width="90" align="center" />
<el-table-column label="场地" width="140" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row.jxcd || '-' }}</template>
<el-table-column prop="kcmc" label="课程" min-width="150" show-overflow-tooltip header-align="center" />
<el-table-column prop="kcxh" label="课次序号" width="90" align="center" />
<el-table-column label="教学内容" min-width="180" show-overflow-tooltip header-align="center">
<template slot-scope="scope">{{ scope.row.jxnr || '-' }}</template>
</el-table-column>
<el-table-column label="教学内容方法" min-width="150" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row._jxnrff || '-' }}</template>
<el-table-column label="教学方法" width="130" align="center" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row.jxff || '-' }}</template>
</el-table-column>
<el-table-column prop="jybz" label="备注" width="100" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row.jybz || '-' }}</template>
</el-table-column>
<el-table-column label="计划变更明细" width="120" align="center">
<el-table-column label="填写状态" width="100" align="center">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="openDetail(scope.row)">计划变更明细</el-button>
</template>
</el-table-column>
<el-table-column label="操作" width="180" align="center">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="openRemark(scope.row)">编辑备注</el-button>
<el-button type="text" size="mini" class="danger-text" @click="handleComing">删除</el-button>
<el-tag :type="txztType(scope.row.txzt)" size="mini">{{ txztText(scope.row.txzt) }}</el-tag>
</template>
</el-table-column>
</el-table>
<el-empty v-show="!loading && !weekList.length" description="本周暂无教学计划数据" />
<el-empty v-show="!weekLoading && !weekList.length" description="本周暂无教学计划数据" />
<pagination
v-show="weekTotal > 0"
:total="weekTotal"
@@ -150,58 +90,36 @@
<el-link type="primary" :underline="false" class="nav-link" @click="handleNextWeek3">下一周</el-link>
</div>
<!-- 批量操作栏 -->
<div class="toolbar-card">
<div class="toolbar">
<div class="toolbar-left">
<el-button type="primary" size="mini" :disabled="!practiceSelected.length" @click="handleComing">批量设置备注信息</el-button>
<el-button type="primary" size="mini" :disabled="!practiceSelected.length" @click="handleComing">批量变更时间</el-button>
<el-button type="primary" size="mini" :disabled="!practiceSelected.length" @click="handleComing">批量平移时间</el-button>
<el-button type="primary" size="mini" :disabled="!practiceSelected.length" @click="handleComing">批量变更授课教员</el-button>
<el-button type="primary" size="mini" :disabled="!practiceSelected.length" @click="handleComing">批量变更授课场地</el-button>
<el-button type="danger" size="mini" :disabled="!practiceSelected.length" @click="handleComing">批量删除</el-button>
</div>
<div class="toolbar-right">
<el-button type="primary" size="mini" @click="handleComing">报批下载</el-button>
<el-button type="primary" size="mini" @click="handleComing">下载</el-button>
</div>
</div>
</div>
<el-alert
class="mb8"
type="info"
:closable="false"
show-icon
title="说明:后端教学实施计划课次接口未返回课程类型字段,无法按三实课类型筛选,当前展示本周全部教学实施计划课次。"
/>
<!-- 三实表格 -->
<div class="table-card">
<el-table :data="practiceList" v-loading="loading" stripe border @selection-change="practiceSelected = $event">
<el-table-column type="selection" width="50" align="center" />
<el-table :data="practiceList" v-loading="practiceLoading" stripe border>
<el-table-column type="index" label="序号" width="55" align="center" />
<el-table-column label="上课时间" width="170" align="center" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row._sjsj || '-' }}</template>
<el-table-column label="上课时间" width="200" align="center" show-overflow-tooltip>
<template slot-scope="scope">{{ fmtLessonTime(scope.row) }}</template>
</el-table-column>
<el-table-column prop="kcmc" label="课程" min-width="130" show-overflow-tooltip />
<el-table-column prop="zrdw" label="责任单位" width="100" align="center" />
<el-table-column prop="bc" label="班次" width="130" show-overflow-tooltip />
<el-table-column prop="skjy" label="教员" width="90" align="center" />
<el-table-column label="场地" width="140" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row.jxcd || '-' }}</template>
<el-table-column prop="kcmc" label="课程" min-width="150" show-overflow-tooltip header-align="center" />
<el-table-column prop="kcxh" label="课次序号" width="90" align="center" />
<el-table-column label="教学内容" min-width="180" show-overflow-tooltip header-align="center">
<template slot-scope="scope">{{ scope.row.jxnr || '-' }}</template>
</el-table-column>
<el-table-column label="教学内容方法" min-width="150" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row._jxnrff || '-' }}</template>
<el-table-column label="教学方法" width="130" align="center" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row.jxff || '-' }}</template>
</el-table-column>
<el-table-column prop="jybz" label="备注" width="100" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row.jybz || '-' }}</template>
</el-table-column>
<el-table-column label="计划变更明细" width="120" align="center">
<el-table-column label="填写状态" width="100" align="center">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="openDetail(scope.row)">计划变更明细</el-button>
</template>
</el-table-column>
<el-table-column label="操作" width="180" align="center">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="openRemark(scope.row)">编辑备注</el-button>
<el-button type="text" size="mini" class="danger-text" @click="handleComing">删除</el-button>
<el-tag :type="txztType(scope.row.txzt)" size="mini">{{ txztText(scope.row.txzt) }}</el-tag>
</template>
</el-table-column>
</el-table>
<el-empty v-show="!loading && !practiceList.length" description="本周暂无三实教学计划数据" />
<el-empty v-show="!practiceLoading && !practiceList.length" description="本周暂无三实教学计划数据" />
<pagination
v-show="practiceTotal > 0"
:total="practiceTotal"
@@ -219,122 +137,115 @@
<!-- 查询条件 -->
<el-form :inline="true" class="shift-search">
<el-form-item label="队别班次">
<el-select v-model="shiftTeamClass" placeholder="请选择队别班次" clearable class="shift-select">
<el-option label="一班" value="一班" />
<el-option label="二班" value="二班" />
<el-option label="三班" value="三班" />
<el-option label="四班" value="四班" />
<el-select
v-model="shiftXydbh"
placeholder="请选择队别班次"
clearable
filterable
class="shift-select"
>
<el-option v-for="t in teamOptions" :key="t.xydbh" :label="t.xydmc" :value="t.xydbh" />
</el-select>
</el-form-item>
<el-form-item label="年度">
<el-select v-model="shiftNd" placeholder="请选择年度" class="shift-select">
<el-option v-for="y in yearOptions" :key="y" :label="y" :value="y" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleComing">查询</el-button>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleShiftSearch">查询</el-button>
</el-form-item>
</el-form>
<!-- 标题 -->
<div class="shift-title">2026年秋季学期{{ shiftTeamClass || '' }}课程表</div>
<div class="shift-title">{{ shiftTitle }}</div>
<!-- 操作按钮 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-download" size="mini" @click="handleComing">学期教学实施计划</el-button>
<el-button type="primary" plain icon="el-icon-download" size="mini" @click="handleNotProvided">学期教学实施计划</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-download" size="mini" @click="handleComing">学期课程表(周次星期)</el-button>
<el-button type="primary" plain icon="el-icon-download" size="mini" @click="handleNotProvided">学期课程表(周次星期)</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-download" size="mini" @click="handleComing">学期课程表(班次视图)</el-button>
<el-button type="primary" plain icon="el-icon-download" size="mini" @click="handleNotProvided">学期课程表(班次视图)</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-download" size="mini" @click="handleComing">学期课程表(排课周)含选修</el-button>
<el-button type="primary" plain icon="el-icon-download" size="mini" @click="handleNotProvided">学期课程表(排课周)含选修</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-download" size="mini" @click="handleComing">学期教学实施计划(含选修)</el-button>
<el-button type="primary" plain icon="el-icon-download" size="mini" @click="handleNotProvided">学期教学实施计划(含选修)</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-download" size="mini" @click="handleComing">学期课程表(周次星期)详细</el-button>
<el-button type="primary" plain icon="el-icon-download" size="mini" @click="handleNotProvided">学期课程表(周次星期)详细</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-download" size="mini" @click="handleComing">学期成绩表</el-button>
<el-button type="primary" plain icon="el-icon-download" size="mini" @click="handleNotProvided">学期成绩表</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="el-icon-download" size="mini" @click="handleComing">课程列表另存Excel</el-button>
<el-button type="success" plain icon="el-icon-download" size="mini" @click="handleNotProvided">课程列表另存Excel</el-button>
</el-col>
</el-row>
<!-- 班次课程表 -->
<el-table v-loading="loading" :data="shiftList" border stripe>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column label="课程名称/课时系数" min-width="220">
<template slot-scope="scope">
<div>{{ scope.row.kcmc }}</div>
<div class="sub-text">{{ scope.row.kssk }}</div>
</template>
</el-table-column>
<el-table-column prop="zrjs" label="责任教员/课次" min-width="150" />
<el-table-column prop="bc" label="班次" min-width="120" />
<el-table-column prop="nj" label="年级" width="80" align="center" />
<el-table-column prop="zy" label="专业" min-width="120" />
<el-table-column prop="jhxs" label="计划学时" width="90" align="center" />
<el-table-column prop="yxxs" label="运行学时" width="90" align="center" />
<el-table-column prop="khlxfs" label="考核类型方式" min-width="140" />
<el-table-column prop="ssjs" label="实施教员" min-width="120" />
<el-table-column prop="rscd" label="人数/场地" min-width="140" />
<el-table-column prop="kcjd" label="课程进度" width="120" align="center" />
<el-table-column prop="ssjhbg" label="实施计划变更" min-width="120" align="center" />
</el-table>
<div class="table-card">
<el-table v-loading="shiftLoading" :data="shiftList" border stripe>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column label="课程名称/简称" min-width="200" header-align="center">
<template slot-scope="scope">
<div>{{ scope.row._kcmc || '-' }}</div>
<div class="sub-text">{{ scope.row.jc || '-' }}</div>
</template>
</el-table-column>
<el-table-column label="责任教员/课次" width="160" header-align="center">
<template slot-scope="scope">
<div>{{ scope.row._zrjy || '-' }}</div>
<div class="sub-text">第{{ scope.row.kcxh || '-' }}次课</div>
</template>
</el-table-column>
<el-table-column prop="xydmc" label="班次" width="130" show-overflow-tooltip header-align="center" />
<el-table-column prop="xs" label="计划学时" width="90" align="center" />
<el-table-column prop="llxs" label="理论学时" width="90" align="center" />
<el-table-column prop="sjxs" label="实践学时" width="90" align="center" />
<el-table-column prop="cjfz" label="考核类型方式" min-width="110" show-overflow-tooltip header-align="center" />
<el-table-column label="实施教员" width="110" align="center" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row._ssjy || '-' }}</template>
</el-table-column>
<el-table-column label="人数/场地" width="130" header-align="center">
<template slot-scope="scope">
<div>{{ scope.row.rs || '-' }} 人</div>
<div class="sub-text">{{ scope.row.jsbh || '-' }}</div>
</template>
</el-table-column>
<el-table-column prop="xf" label="学分" width="80" align="center" />
<el-table-column label="实施计划变更" width="150" header-align="center">
<template slot-scope="scope">{{ fmtKbbdsj(scope.row.kbbdsj) }}</template>
</el-table-column>
</el-table>
<el-empty v-show="!shiftLoading && !shiftList.length" description="该班次暂无课程数据" />
<pagination
v-show="shiftTotal > 0"
:total="shiftTotal"
:page.sync="shiftQuery.pageNum"
:limit.sync="shiftQuery.pageSize"
@pagination="getShiftList"
/>
</div>
</div>
</el-tab-pane>
</el-tabs>
<!-- 编辑备注弹窗 -->
<el-dialog title="编辑备注" :visible.sync="remarkVisible" width="500px" append-to-body>
<el-form label-width="80px">
<el-form-item label="课程">
<span>{{ remarkTarget.kcmc || '-' }}</span>
</el-form-item>
<el-form-item label="备注内容">
<el-input v-model="remarkText" type="textarea" :rows="4" placeholder="请输入备注内容" maxlength="500" show-word-limit />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="confirmRemark">确定</el-button>
<el-button @click="remarkVisible = false">取消</el-button>
</div>
</el-dialog>
<!-- 计划变更明细弹窗 -->
<el-dialog title="计划变更明细" :visible.sync="detailVisible" width="560px" append-to-body>
<el-descriptions :column="2" border size="small" v-if="detailTarget">
<el-descriptions-item label="课程">{{ detailTarget.kcmc }}</el-descriptions-item>
<el-descriptions-item label="班次">{{ detailTarget.bc }}</el-descriptions-item>
<el-descriptions-item label="责任单位">{{ detailTarget.zrdw }}</el-descriptions-item>
<el-descriptions-item label="上课时间">{{ detailTarget._sjsj || formatTime(detailTarget.jc) }}</el-descriptions-item>
<el-descriptions-item label="原节次">{{ detailTarget.yjc || '-' }}</el-descriptions-item>
<el-descriptions-item label="授课教员">{{ detailTarget.skjy }}</el-descriptions-item>
<el-descriptions-item label="教学场地">{{ detailTarget.jxcd || '-' }}</el-descriptions-item>
<el-descriptions-item label="检查结果">{{ detailTarget.jcjg || '-' }}</el-descriptions-item>
<el-descriptions-item label="已通过检查" :span="2">
<el-tag :type="detailTarget.ytgjc ? 'success' : 'info'" size="mini">{{ detailTarget.ytgjc ? '是' : '否' }}</el-tag>
</el-descriptions-item>
</el-descriptions>
</el-dialog>
</div>
</template>
<script>
// 节次 -> 时间映射
const JC_TIME = {
1: '08:00-08:45',
2: '08:55-09:40',
3: '10:00-10:45',
4: '10:55-11:40',
5: '14:30-15:15',
6: '15:25-16:10',
7: '16:20-17:05',
8: '19:00-19:45',
9: '19:55-20:40'
}
import { listTeachingPlan } from '@/api/teachBusiness/courseRunning'
import { listStudentTeamTask } from '@/api/teachBusiness/studentTeamTask'
import { listTeam } from '@/api/studentRecords/team'
import { listSubject } from '@/api/teachOffice/subject'
import { listTeacher } from '@/api/teachOffice/teacher'
import { listAllSemester } from '@/api/teachBusiness/semester'
// 节次 -> 节次区间映射
const JC_SECTION = {
1: '1-2节', 2: '3-4节', 3: '5-6节', 4: '7-8节', 5: '9-10节', 6: '11-12节'
@@ -362,32 +273,36 @@ export default {
data() {
return {
activeTab: 'today',
loading: false,
// 今日
todayDate: new Date(),
todayList: [],
todaySelected: [],
todayLoading: false,
// 本周
weekStart: getMonday(new Date()),
weekList: [],
weekSelected: [],
weekLoading: false,
weekTotal: 0,
weekQuery: { pageNum: 1, pageSize: 20 },
// 三实
practiceStart: getMonday(new Date()),
practiceList: [],
practiceSelected: [],
practiceLoading: false,
practiceTotal: 0,
practiceQuery: { pageNum: 1, pageSize: 20 },
// 班次(仅占位数据,待对接接口)
shiftTeamClass: '',
// 班次
teamOptions: [],
shiftXydbh: '',
shiftNd: undefined,
shiftList: [],
// 弹窗
remarkVisible: false,
remarkTarget: {},
remarkText: '',
detailVisible: false,
detailTarget: null
shiftLoading: false,
shiftTotal: 0,
shiftQuery: { pageNum: 1, pageSize: 20 },
// 年度下拉数据来自 /semester/all(真实后端数据)
yearOptions: [],
defaultNd: undefined,
// 名称映射(真实接口数据,用于班次 tab 课程/教员编号转名称)
kbMap: {},
teacherMap: {}
}
},
computed: {
@@ -405,32 +320,75 @@ export default {
end.setDate(end.getDate() + 6)
return formatDate(this.practiceStart) + '(' + this.weekDayName(this.practiceStart) + ') 至 ' +
formatDate(end) + '(' + this.weekDayName(end) + ') 三实教学实施计划'
},
shiftTitle() {
const team = this.teamOptions.find(t => t.xydbh === this.shiftXydbh)
const name = (team && team.xydmc) || ''
return (this.shiftNd ? this.shiftNd + '年' : '') + name + '课程表'
}
},
created() {
this.loadYearOptions()
this.getTodayList()
this.getWeekList()
this.getPracticeList()
this.loadTeamOptions()
this.loadKbMap()
this.loadTeacherMap()
},
methods: {
/* ---------- 年度下拉(数据来自 /semester/all) ---------- */
loadYearOptions() {
return listAllSemester().then(response => {
const list = response.data || []
// 去重并按年度倒序
const map = {}
list.forEach(item => {
if (item.nd !== null && item.nd !== undefined && item.nd !== '') map[item.nd] = item
})
const arr = Object.keys(map).sort((a, b) => String(b).localeCompare(String(a)))
this.yearOptions = arr
// 默认年度:优先当前学期(dqxq=true),否则取最新年度
const current = list.find(item => item.dqxq === true)
this.defaultNd = (current && current.nd) || arr[0]
if (!this.shiftNd) this.shiftNd = this.defaultNd
return arr
}).catch(() => {
this.yearOptions = []
this.defaultNd = undefined
return []
})
},
weekDayName(d) {
return WEEK_DAY_NAMES[d.getDay()]
},
formatTime(jc) {
if (!jc) return '-'
return JC_TIME[jc] || ('第' + jc + '节')
},
formatSection(jc) {
if (!jc) return ''
if (jc === null || jc === undefined || jc === '') return '第?节'
return JC_SECTION[jc] || ('第' + jc + '节')
},
buildSjsj(rq, jc) {
const date = (rq || '').substring(0, 10)
/** 后端 LocalDateTime -> 日期+周X+节次区间 */
fmtLessonTime(row) {
const rq = row.rq || ''
const date = String(rq).substring(0, 10)
if (!date) return '-'
const d = new Date(date.replace(/-/g, '/'))
const wn = WEEK_DAY_NAMES[d.getDay()]
const section = this.formatSection(jc)
return (date + ' 周' + wn + ' ' + section).trim()
return (date + ' 周' + wn + ' ' + this.formatSection(row.jc)).trim()
},
/** 后端 LocalDateTime -> yyyy-MM-dd HH:mm */
fmtKbbdsj(v) {
if (!v) return '-'
return String(v).replace('T', ' ').substring(0, 16)
},
txztText(txzt) {
if (txzt === 1) return '已填写'
if (txzt === 2) return '已提交'
return '未填写'
},
txztType(txzt) {
if (txzt === 1) return 'success'
if (txzt === 2) return 'primary'
return 'info'
},
/* ---------- 今日 ---------- */
handlePrevDay() {
@@ -446,115 +404,178 @@ export default {
this.getTodayList()
},
getTodayList() {
this.loading = true
setTimeout(() => {
this.todayList = this.buildMockList(this.todayDate)
this.loading = false
}, 200)
const day = formatDate(this.todayDate)
const params = {
rqStart: day + 'T00:00:00',
rqEnd: day + 'T23:59:59',
pageNum: 1,
pageSize: 200
}
this.todayLoading = true
listTeachingPlan(params).then(response => {
const data = response.data || {}
this.todayList = data.records || []
this.todayLoading = false
}).catch(() => {
this.todayList = []
this.todayLoading = false
})
},
/* ---------- 本周 ---------- */
handlePrevWeek() {
const d = new Date(this.weekStart)
d.setDate(d.getDate() - 7)
this.weekStart = d
this.weekQuery.pageNum = 1
this.getWeekList()
},
handleNextWeek() {
const d = new Date(this.weekStart)
d.setDate(d.getDate() + 7)
this.weekStart = d
this.weekQuery.pageNum = 1
this.getWeekList()
},
weekRange() {
const start = formatDate(this.weekStart) + 'T00:00:00'
const endDate = new Date(this.weekStart)
endDate.setDate(endDate.getDate() + 6)
const end = formatDate(endDate) + 'T23:59:59'
return { start: start, end: end }
},
getWeekList() {
this.loading = true
setTimeout(() => {
this.weekList = this.buildMockWeekList(this.weekStart)
this.weekTotal = this.weekList.length
this.loading = false
}, 200)
const range = this.weekRange()
const params = {
rqStart: range.start,
rqEnd: range.end,
pageNum: this.weekQuery.pageNum,
pageSize: this.weekQuery.pageSize
}
this.weekLoading = true
listTeachingPlan(params).then(response => {
const data = response.data || {}
this.weekList = data.records || []
this.weekTotal = data.total || 0
this.weekLoading = false
}).catch(() => {
this.weekList = []
this.weekTotal = 0
this.weekLoading = false
})
},
/* ---------- 三实 ---------- */
handlePrevWeek3() {
const d = new Date(this.practiceStart)
d.setDate(d.getDate() - 7)
this.practiceStart = d
this.practiceQuery.pageNum = 1
this.getPracticeList()
},
handleNextWeek3() {
const d = new Date(this.practiceStart)
d.setDate(d.getDate() + 7)
this.practiceStart = d
this.practiceQuery.pageNum = 1
this.getPracticeList()
},
practiceRange() {
const start = formatDate(this.practiceStart) + 'T00:00:00'
const endDate = new Date(this.practiceStart)
endDate.setDate(endDate.getDate() + 6)
const end = formatDate(endDate) + 'T23:59:59'
return { start: start, end: end }
},
getPracticeList() {
this.loading = true
setTimeout(() => {
this.practiceList = this.buildMockWeekList(this.practiceStart)
this.practiceTotal = this.practiceList.length
this.loading = false
}, 200)
},
/* ---------- 静态示例数据(待对接接口) ---------- */
buildMockList(date) {
const dateStr = formatDate(date)
const samples = [
{ jc: 1, kcmc: '高等数学', zrdw: '基础部', bc: '一班', skjy: '张教员', jxcd: '1号教学楼-101', jxnr: '函数与极限', jxff: '讲授', jybz: '携带教材' },
{ jc: 3, kcmc: '大学英语', zrdw: '基础部', bc: '二班', skjy: '李教员', jxcd: '2号教学楼-205', jxnr: 'Unit 3 课文精读', jxff: '讲授/讨论', jybz: '' },
{ jc: 5, kcmc: '计算机基础', zrdw: '信息工程系', bc: '三班', skjy: '王教员', jxcd: '实验楼-303', jxnr: 'Word 文档排版', jxff: '实操', jybz: '机房上课' }
]
return samples.map((item, index) => ({
...item,
xh: index + 1,
rq: dateStr,
_jxnrff: [item.jxnr, item.jxff].filter(Boolean).join(' / ')
}))
},
buildMockWeekList(weekStart) {
const list = []
const dates = []
for (let i = 0; i < 7; i++) {
const d = new Date(weekStart)
d.setDate(d.getDate() + i)
dates.push(formatDate(d))
// 后端 plan/list 接口未返回课程类型字段,无法按三实课筛选,与本周共用同一数据源
const range = this.practiceRange()
const params = {
rqStart: range.start,
rqEnd: range.end,
pageNum: this.practiceQuery.pageNum,
pageSize: this.practiceQuery.pageSize
}
const courses = [
{ jc: 1, kcmc: '高等数学', zrdw: '基础部', bc: '一班', skjy: '张教员', jxcd: '1号教学楼-101', jxnr: '函数与极限', jxff: '讲授', jybz: '携带教材' },
{ jc: 2, kcmc: '大学英语', zrdw: '基础部', bc: '二班', skjy: '李教员', jxcd: '2号教学楼-205', jxnr: 'Unit 3 课文精读', jxff: '讲授/讨论', jybz: '' },
{ jc: 3, kcmc: '计算机基础', zrdw: '信息工程系', bc: '三班', skjy: '王教员', jxcd: '实验楼-303', jxnr: 'Word 文档排版', jxff: '实操', jybz: '机房上课' },
{ jc: 4, kcmc: '军事理论', zrdw: '政工系', bc: '一班', skjy: '赵教员', jxcd: '3号教学楼-110', jxnr: '国防动员概述', jxff: '讲授', jybz: '' }
]
dates.forEach((dateStr, i) => {
courses.forEach((item, idx) => {
// 隔天轮换部分课程,保证每周有内容且不重复
if ((i + idx) % 2 === 0) {
list.push({
...item,
xh: list.length + 1,
rq: dateStr,
_sjsj: this.buildSjsj(dateStr, item.jc),
_jxnrff: [item.jxnr, item.jxff].filter(Boolean).join(' / ')
})
}
})
this.practiceLoading = true
listTeachingPlan(params).then(response => {
const data = response.data || {}
this.practiceList = data.records || []
this.practiceTotal = data.total || 0
this.practiceLoading = false
}).catch(() => {
this.practiceList = []
this.practiceTotal = 0
this.practiceLoading = false
})
return list
},
/* ---------- 弹窗 ---------- */
openRemark(row) {
this.remarkTarget = row
this.remarkText = row.jybz || ''
this.remarkVisible = true
/* ---------- 班次 ---------- */
loadTeamOptions() {
listTeam({ pageNum: 1, pageSize: 1000 }).then(response => {
const data = response.data || {}
this.teamOptions = data.records || []
}).catch(() => {
this.teamOptions = []
})
},
confirmRemark() {
this.$message.success('备注已更新')
this.remarkVisible = false
loadKbMap() {
listSubject({ pageNum: 1, pageSize: 1000 }).then(response => {
const data = response.data || {}
const map = {}
;(data.records || []).forEach(item => {
if (item.kbh) map[item.kbh] = item.kmc
})
this.kbMap = map
}).catch(() => {
this.kbMap = {}
})
},
openDetail(row) {
this.detailTarget = row
this.detailVisible = true
loadTeacherMap() {
listTeacher({ pageNum: 1, pageSize: 1000 }).then(response => {
const data = response.data || {}
const map = {}
;(data.records || []).forEach(item => {
if (item.jybh) map[item.jybh] = item.jyxm
})
this.teacherMap = map
}).catch(() => {
this.teacherMap = {}
})
},
handleComing() {
this.$message.info('功能建设中,敬请期待')
handleShiftSearch() {
if (!this.shiftXydbh) {
this.$message.warning('请先选择队别班次')
return
}
this.shiftQuery.pageNum = 1
this.getShiftList()
},
getShiftList() {
const params = {
xydbh: this.shiftXydbh,
nd: this.shiftNd,
pageNum: this.shiftQuery.pageNum,
pageSize: this.shiftQuery.pageSize
}
this.shiftLoading = true
listStudentTeamTask(params).then(response => {
const data = response.data || {}
const team = this.teamOptions.find(t => t.xydbh === this.shiftXydbh)
this.shiftList = (data.records || []).map(item => ({
...item,
xydmc: (team && team.xydmc) || item.xydbh,
_kcmc: this.kbMap[item.kbh] || item.kbh,
_zrjy: this.teacherMap[item.jysjhjybh] || item.jysjhjybh || '-',
_ssjy: this.teacherMap[item.jybh] || item.jybh || '-'
}))
this.shiftTotal = data.total || 0
this.shiftLoading = false
}).catch(() => {
this.shiftList = []
this.shiftTotal = 0
this.shiftLoading = false
})
},
/* ---------- 无后端接口的操作 ---------- */
handleNotProvided() {
this.$message.info('后端暂未提供该接口')
}
}
}
@@ -573,6 +594,10 @@ export default {
padding-bottom: 16px;
}
.mb8 {
margin-bottom: 8px;
}
/* 日期/周次导航 */
.nav-header {
display: flex;
@@ -596,31 +621,6 @@ export default {
}
}
/* 批量操作栏 */
.toolbar-card {
margin-bottom: 12px;
padding: 10px 14px;
background: #fff;
border: 1px solid #ebeef5;
border-radius: 4px;
.toolbar {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 10px;
.toolbar-left,
.toolbar-right {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 10px;
}
}
}
/* 表格区 */
.table-card {
.danger-text {
@@ -1,8 +1,8 @@
<template>
<div class="page-container">
<!-- 近期申请统计卡片 -->
<!-- 统计卡片 -->
<div class="section-card">
<div class="section-title">近期课表调整申请</div>
<div class="section-title">调课申请统计(当前页)</div>
<div class="stat-grid">
<div
v-for="card in statCards"
@@ -22,19 +22,18 @@
<!-- 查询条件 -->
<div class="section-card">
<el-form :model="queryParams" :inline="true" size="small" class="search-form">
<el-form-item label="课程名称">
<el-input v-model="queryParams.kcmc" placeholder="请输入课程名称" clearable style="width: 180px" @keyup.enter.native="handleQuery" />
<el-form :model="queryParams" :inline="true" size="small" class="search-form" @submit.native.prevent>
<el-form-item label="年度">
<el-select v-model="queryParams.nd" placeholder="请选择年度" clearable style="width: 140px">
<el-option v-for="y in yearOptions" :key="y" :label="y" :value="y" />
</el-select>
</el-form-item>
<el-form-item label="队别(班次)">
<el-input v-model="queryParams.dbbc" placeholder="请输入队别班次" clearable style="width: 160px" @keyup.enter.native="handleQuery" />
<el-form-item label="申请教员编号">
<el-input v-model="queryParams.sqjybh" placeholder="请输入申请教员编号" clearable style="width: 180px" @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="申请教员">
<el-input v-model="queryParams.sqjy" placeholder="请输入申请教员" clearable style="width: 160px" @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="状态">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable style="width: 140px">
<el-option v-for="item in statusOptions" :key="item.value" :label="item.label" :value="item.value" />
<el-form-item label="教研室审批状态">
<el-select v-model="queryParams.jyspzzt" placeholder="请选择" clearable style="width: 150px">
<el-option v-for="item in auditStatusOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item>
@@ -53,87 +52,270 @@
</div>
</div>
<el-table :data="filteredData" v-loading="loading" size="small" border stripe class="compact-table" max-height="520">
<el-table :data="tableData" v-loading="loading" size="small" border stripe class="compact-table" max-height="520">
<el-table-column type="index" label="序号" width="55" align="center" />
<el-table-column prop="kcmc" label="课程名称" min-width="140" show-overflow-tooltip />
<el-table-column prop="jxsj" label="教学时间" width="110" align="center" />
<el-table-column prop="sksj" label="上课时间" width="180" />
<el-table-column prop="jy" label="教员" width="90" align="center" />
<el-table-column prop="jxcd" label="教学场地" width="120" show-overflow-tooltip />
<el-table-column prop="dbbc" label="队别(班次)" min-width="140" show-overflow-tooltip />
<el-table-column prop="sqjy" label="申请教员" width="90" align="center" />
<el-table-column prop="zxkb" label="拟调整时间" width="180" />
<el-table-column label="申请类型" width="90" align="center">
<el-table-column prop="kcmc" label="课程名称" min-width="140" show-overflow-tooltip header-align="center" />
<el-table-column label="申请教员" min-width="110" align="center" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row.sqjyxm || scope.row.sqjybh || '-' }}</template>
</el-table-column>
<el-table-column prop="sy" label="调课事由" min-width="180" show-overflow-tooltip header-align="center" />
<el-table-column label="拟调整时间" width="170" align="center" show-overflow-tooltip>
<template slot-scope="scope">{{ fmtRqJc(scope.row.rq, scope.row.jc) }}</template>
</el-table-column>
<el-table-column prop="nd" label="年度" width="80" align="center" />
<el-table-column label="教研室审批状态" width="110" align="center">
<template slot-scope="scope">
<el-tag :type="typeTag(scope.row.lx)" size="mini">{{ scope.row.lx }}</el-tag>
<el-tag :type="auditStatusTag(scope.row.jyspzzt).type" size="mini">{{ auditStatusTag(scope.row.jyspzzt).label }}</el-tag>
</template>
</el-table-column>
<el-table-column label="状态" width="90" align="center">
<el-table-column label="教研室查收状态" width="100" align="center">
<template slot-scope="scope">
<el-tag :type="statusTag(scope.row.status).type" size="mini">{{ statusTag(scope.row.status).label }}</el-tag>
<el-tag :type="receiveStatusTag(scope.row.jyscszt).type" size="mini">{{ receiveStatusTag(scope.row.jyscszt).label }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center" fixed="right">
<el-table-column label="创建时间" width="150" align="center">
<template slot-scope="scope">{{ fmtDateTime(scope.row.cjsj) }}</template>
</el-table-column>
<el-table-column label="操作" width="210" align="center" fixed="right">
<template slot-scope="scope">
<el-button type="text" size="mini" icon="el-icon-view" @click="openDetail(scope.row)">查看</el-button>
<template v-if="scope.row.status === '0'">
<el-button type="text" size="mini" icon="el-icon-check" @click="approve(scope.row)">批准</el-button>
<el-button type="text" size="mini" icon="el-icon-close" class="danger-btn" @click="reject(scope.row)">驳回</el-button>
</template>
<el-button v-else type="text" size="mini" class="danger-btn" @click="removeRow(scope.row)">删除</el-button>
<div class="ops-cell">
<el-button type="text" size="mini" icon="el-icon-view" @click="openDetail(scope.row)">详情</el-button>
<el-dropdown v-if="showAuditMenu(scope.row)" trigger="click" @command="cmd => openAudit(scope.row, cmd)">
<el-button type="text" size="mini" @click.stop>审批<i class="el-icon-arrow-down el-icon--right" /></el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-if="scope.row.jyspzzt === 0" command="jys">教研室审批</el-dropdown-item>
<el-dropdown-item v-if="scope.row.jyspzzt === 1" command="jxx">教学系审批</el-dropdown-item>
<el-dropdown-item v-if="scope.row.jyspzzt === 1" command="jg">机关审批</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<el-dropdown v-if="showReceiveMenu(scope.row)" trigger="click" @command="cmd => openReceive(scope.row, cmd)">
<el-button type="text" size="mini" @click.stop>查收<i class="el-icon-arrow-down el-icon--right" /></el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-if="scope.row.jyscszt === 0" command="jys">教研室查收</el-dropdown-item>
<el-dropdown-item command="jg">机关查收</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<el-button v-if="scope.row.sczt === 0 && scope.row.jyspzzt !== 1" type="text" size="mini" class="danger-btn" @click="handleCancel(scope.row)">撤销</el-button>
</div>
</template>
</el-table-column>
</el-table>
<el-empty v-show="!loading && filteredData.length === 0" description="暂无调整申请" />
<el-empty v-show="!loading && tableData.length === 0" description="暂无调整申请" />
<pagination
v-show="total > 0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="loadList"
/>
</div>
<!-- 新增申请弹窗 -->
<el-dialog title="新增课表调整申请" :visible.sync="dialogVisible" width="640px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
<el-form-item label="课程名称" prop="kcmc">
<el-input v-model="form.kcmc" placeholder="请输入课程名称" />
<el-dialog title="新增课表调整申请" :visible.sync="addVisible" width="640px" append-to-body>
<el-form ref="addForm" :model="addForm" :rules="addRules" label-width="110px">
<el-form-item label="年度" prop="nd">
<el-select v-model="addForm.nd" placeholder="请选择年度" style="width: 100%">
<el-option v-for="y in yearOptions" :key="y" :label="y" :value="y" />
</el-select>
</el-form-item>
<el-form-item label="申请类型" prop="lx">
<el-radio-group v-model="form.lx">
<el-radio label="调课" />
<el-radio label="停课" />
<el-radio label="补课" />
</el-radio-group>
<el-form-item label="课次编号" prop="sskcbbh">
<el-input v-model="addForm.sskcbbh" placeholder="请输入课次编号" />
<el-button type="primary" plain size="mini" class="pick-lesson-btn" :loading="lessonLoading" @click="loadAdjustableLessons">选择可供调课课次</el-button>
</el-form-item>
<el-form-item label="队别(班次)" prop="dbbc">
<el-input v-model="form.dbbc" placeholder="请输入队别班次" />
<el-form-item label="调课事由" prop="sy">
<el-input v-model="addForm.sy" type="textarea" :rows="2" placeholder="请输入调课事由" />
</el-form-item>
<el-form-item label="原上课时间" prop="sksj">
<el-input v-model="form.sksj" placeholder="如:周一 第3-4节" />
<el-form-item label="拟调整日期" prop="rq">
<el-date-picker v-model="addForm.rq" type="date" value-format="yyyy-MM-dd" placeholder="请选择日期" style="width: 100%" />
</el-form-item>
<el-form-item label="拟调整时间" prop="zxkb">
<el-input v-model="form.zxkb" placeholder="请输入拟调整后的上课时间" />
<el-form-item label="拟调整节次" prop="jc">
<el-select v-model="addForm.jc" placeholder="请选择节次" style="width: 100%">
<el-option v-for="n in jcOptions" :key="n" :label="jcLabel(n)" :value="n" />
</el-select>
</el-form-item>
<el-form-item label="申请理由" prop="bz">
<el-input v-model="form.bz" type="textarea" :rows="3" placeholder="请输入申请理由" />
<el-form-item label="申请教员编号" prop="sqjybh">
<el-input v-model="addForm.sqjybh" placeholder="请输入申请教员编号" />
</el-form-item>
<el-form-item label="教学内容">
<el-input v-model="addForm.jxnr" placeholder="选填" />
</el-form-item>
<el-form-item label="教学要点">
<el-input v-model="addForm.jxyd" placeholder="选填" />
</el-form-item>
<el-form-item label="教学方法">
<el-input v-model="addForm.jxff" placeholder="选填" />
</el-form-item>
<el-form-item label="教学保障备注">
<el-input v-model="addForm.jxbzbz" placeholder="选填" />
</el-form-item>
<el-form-item label="用车信息">
<el-input v-model="addForm.ycxx" placeholder="选填" />
</el-form-item>
<el-form-item label="备注">
<el-input v-model="addForm.bz" type="textarea" :rows="2" placeholder="选填" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" :loading="submitLoading" @click="submitForm">提交申请</el-button>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" :loading="addLoading" @click="submitAdd">提交申请</el-button>
<el-button @click="addVisible = false">取消</el-button>
</div>
</el-dialog>
<!-- 可供调课课次选择弹窗 -->
<el-dialog title="选择可供调课课次" :visible.sync="lessonDialogVisible" width="760px" append-to-body>
<el-table :data="lessonOptions" v-loading="lessonLoading" size="small" border stripe max-height="420">
<el-table-column prop="kcmc" label="课程名称" min-width="140" show-overflow-tooltip header-align="center" />
<el-table-column label="原上课时间" width="160" align="center">
<template slot-scope="scope">{{ fmtRqJc(scope.row.rq, scope.row.jc) }}</template>
</el-table-column>
<el-table-column prop="xydmc" label="学员队" min-width="120" show-overflow-tooltip header-align="center" />
<el-table-column prop="kcxh" label="课次序号" width="90" align="center" />
<el-table-column prop="jsbh" label="教室编号" width="110" show-overflow-tooltip header-align="center" />
<el-table-column label="操作" width="90" align="center">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="pickLesson(scope.row)">选择</el-button>
</template>
</el-table-column>
</el-table>
<el-empty v-show="!lessonLoading && lessonOptions.length === 0" description="暂无可供调课课次" />
</el-dialog>
<!-- 审批弹窗 -->
<el-dialog :title="auditTitle" :visible.sync="auditVisible" width="520px" append-to-body>
<el-form ref="auditForm" :model="auditForm" label-width="100px">
<el-form-item label="审批结果" required>
<el-radio-group v-model="auditForm.spzt">
<el-radio :label="1">同意</el-radio>
<el-radio :label="2">发回</el-radio>
<el-radio :label="3">拒绝</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="审批意见">
<el-input v-model="auditForm.fhyj" type="textarea" :rows="3" placeholder="请输入审批意见(发回/拒绝时必填)" />
</el-form-item>
<el-form-item label="审批人编号" required>
<el-input v-model="auditForm.sprbh" placeholder="请输入审批人编号" />
</el-form-item>
<el-form-item v-if="auditForm.auditRole === 'jg'" label="机关编号" required>
<el-input v-model="auditForm.jgbh" placeholder="请输入机关编号" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" :loading="auditLoading" @click="submitAudit">确定</el-button>
<el-button @click="auditVisible = false">取消</el-button>
</div>
</el-dialog>
<!-- 查收弹窗 -->
<el-dialog :title="receiveTitle" :visible.sync="receiveVisible" width="460px" append-to-body>
<el-form ref="receiveForm" :model="receiveForm" label-width="100px">
<el-form-item v-if="receiveForm.type === 'jg'" label="机关编号" required>
<el-input v-model="receiveForm.jgbh" placeholder="请输入机关编号" />
</el-form-item>
<el-form-item label="查收人编号" required>
<el-input v-model="receiveForm.csrbh" placeholder="请输入查收人编号" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" :loading="receiveLoading" @click="submitReceive">确定</el-button>
<el-button @click="receiveVisible = false">取消</el-button>
</div>
</el-dialog>
<!-- 查看详情弹窗 -->
<el-dialog title="申请详情" :visible.sync="detailVisible" width="580px" append-to-body>
<el-descriptions v-if="detailTarget" :column="2" border size="small">
<el-descriptions-item label="课程名称">{{ detailTarget.kcmc }}</el-descriptions-item>
<el-descriptions-item label="申请类型">
<el-tag :type="typeTag(detailTarget.lx)" size="mini">{{ detailTarget.lx }}</el-tag>
</el-descriptions-item>
<el-descriptions-item label="队别(班次)">{{ detailTarget.dbbc }}</el-descriptions-item>
<el-descriptions-item label="教员">{{ detailTarget.jy }} / {{ detailTarget.sqjy }}</el-descriptions-item>
<el-descriptions-item label="教学场地">{{ detailTarget.jxcd }}</el-descriptions-item>
<el-descriptions-item label="执行时间">{{ detailTarget.zxsj }}</el-descriptions-item>
<el-descriptions-item label="原上课时间" :span="2">{{ detailTarget.sksj }}</el-descriptions-item>
<el-descriptions-item label="拟调整时间" :span="2">{{ detailTarget.zxkb }}</el-descriptions-item>
<el-descriptions-item label="申请理由" :span="2">{{ detailTarget.bz }}</el-descriptions-item>
</el-descriptions>
<el-dialog title="申请详情" :visible.sync="detailVisible" width="760px" append-to-body>
<div v-loading="detailLoading">
<el-descriptions v-if="detailData" :column="2" border size="small">
<el-descriptions-item label="课程名称">{{ detailData.kcmc || '-' }}</el-descriptions-item>
<el-descriptions-item label="申请教员">{{ detailData.sqjyxm || detailData.sqjybh || '-' }}</el-descriptions-item>
<el-descriptions-item label="调课事由" :span="2">{{ detailData.sy || '-' }}</el-descriptions-item>
<el-descriptions-item label="拟调整时间">{{ fmtRqJc(detailData.rq, detailData.jc) }}</el-descriptions-item>
<el-descriptions-item label="年度">{{ detailData.nd || '-' }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ fmtDateTime(detailData.cjsj) }}</el-descriptions-item>
<el-descriptions-item label="教研室审批状态">
<el-tag :type="auditStatusTag(detailData.jyspzzt).type" size="mini">{{ auditStatusTag(detailData.jyspzzt).label }}</el-tag>
</el-descriptions-item>
<el-descriptions-item label="教研室查收状态">
<el-tag :type="receiveStatusTag(detailData.jyscszt).type" size="mini">{{ receiveStatusTag(detailData.jyscszt).label }}</el-tag>
</el-descriptions-item>
<el-descriptions-item label="教研室审批人">{{ detailData.jyspzrbh || '-' }}</el-descriptions-item>
<el-descriptions-item label="教研室审批时间">{{ fmtDateTime(detailData.jyspzsj) }}</el-descriptions-item>
<el-descriptions-item label="教研室查收人">{{ detailData.jyscsrbh || '-' }}</el-descriptions-item>
<el-descriptions-item label="教研室查收时间">{{ fmtDateTime(detailData.jyscssj) }}</el-descriptions-item>
<el-descriptions-item label="发回意见" :span="2">{{ detailData.fhyj || '-' }}</el-descriptions-item>
<el-descriptions-item label="教学内容" :span="2">{{ detailData.jxnr || '-' }}</el-descriptions-item>
<el-descriptions-item label="教学要点" :span="2">{{ detailData.jxyd || '-' }}</el-descriptions-item>
<el-descriptions-item label="教学方法" :span="2">{{ detailData.jxff || '-' }}</el-descriptions-item>
<el-descriptions-item label="教学保障备注" :span="2">{{ detailData.jxbzbz || '-' }}</el-descriptions-item>
<el-descriptions-item label="用车信息" :span="2">{{ detailData.ycxx || '-' }}</el-descriptions-item>
<el-descriptions-item label="备注" :span="2">{{ detailData.bz || '-' }}</el-descriptions-item>
</el-descriptions>
<!-- 新教室 -->
<div v-if="detailData && detailData.roomList && detailData.roomList.length" class="sub-section">
<div class="sub-title">新教室</div>
<el-table :data="detailData.roomList" size="small" border stripe>
<el-table-column prop="jsbh" label="教室编号" min-width="120" show-overflow-tooltip header-align="center" />
<el-table-column prop="jsmc" label="教室名称" min-width="140" show-overflow-tooltip header-align="center" />
<el-table-column prop="bz" label="备注" min-width="160" show-overflow-tooltip header-align="center" />
</el-table>
</div>
<!-- 新辅助教员 -->
<div v-if="detailData && detailData.teacherList && detailData.teacherList.length" class="sub-section">
<div class="sub-title">新辅助教员</div>
<el-table :data="detailData.teacherList" size="small" border stripe>
<el-table-column prop="fzjybh" label="教员编号" min-width="120" show-overflow-tooltip header-align="center" />
<el-table-column prop="fzjyxm" label="教员姓名" min-width="120" show-overflow-tooltip header-align="center" />
<el-table-column label="是否主教员" width="100" align="center">
<template slot-scope="scope">{{ scope.row.zjy === 1 ? '是' : '否' }}</template>
</el-table-column>
<el-table-column prop="bz" label="备注" min-width="140" show-overflow-tooltip header-align="center" />
</el-table>
</div>
<!-- 新学员队 -->
<div v-if="detailData && detailData.teamList && detailData.teamList.length" class="sub-section">
<div class="sub-title">新学员队</div>
<el-table :data="detailData.teamList" size="small" border stripe>
<el-table-column prop="xydbh" label="学员队编号" min-width="140" show-overflow-tooltip header-align="center" />
<el-table-column prop="xydmc" label="学员队名称" min-width="140" show-overflow-tooltip header-align="center" />
</el-table>
</div>
<!-- 新保障明细 -->
<div v-if="detailData && detailData.supportList && detailData.supportList.length" class="sub-section">
<div class="sub-title">新保障明细</div>
<el-table :data="detailData.supportList" size="small" border stripe>
<el-table-column prop="bztgmxbh" label="保障编号" min-width="140" show-overflow-tooltip header-align="center" />
<el-table-column prop="bztgmxmc" label="保障名称" min-width="160" show-overflow-tooltip header-align="center" />
<el-table-column prop="sl" label="数量" width="80" align="center" />
<el-table-column prop="bz" label="备注" min-width="140" show-overflow-tooltip header-align="center" />
</el-table>
</div>
<!-- 机关审批记录 -->
<div v-if="detailData && detailData.auditList && detailData.auditList.length" class="sub-section">
<div class="sub-title">机关审批记录</div>
<el-table :data="detailData.auditList" size="small" border stripe>
<el-table-column prop="jgmc" label="机关" min-width="140" show-overflow-tooltip header-align="center">
<template slot-scope="scope">{{ scope.row.jgmc || scope.row.jgbh || '-' }}</template>
</el-table-column>
<el-table-column label="审批状态" width="100" align="center">
<template slot-scope="scope">
<el-tag :type="auditStatusTag(scope.row.spzt).type" size="mini">{{ auditStatusTag(scope.row.spzt).label }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="sprxm" label="审批人" min-width="110" show-overflow-tooltip header-align="center">
<template slot-scope="scope">{{ scope.row.sprxm || scope.row.sprbh || '-' }}</template>
</el-table-column>
<el-table-column label="审批时间" width="150" align="center">
<template slot-scope="scope">{{ fmtDateTime(scope.row.spsj) }}</template>
</el-table-column>
<el-table-column prop="fhyj" label="发回意见" min-width="140" show-overflow-tooltip header-align="center" />
</el-table>
</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="detailVisible = false">关闭</el-button>
</div>
@@ -142,175 +324,371 @@
</template>
<script>
// 课表调整申请静态示例数据(待对接 getTimetableAdjustList 接口)
const MOCK_LIST = [
{ id: 1, kcmc: '高等数学', lx: '调课', jxsj: '2026-2027-1', sksj: '周一 第1-2节', zxkb: '周二 第3-4节', jy: '张教员', sqjy: '张教员', jxcd: '1号教学楼-101', dbbc: '一班', zxsj: '2026-09-01', bz: '因学院集中活动调整上课时间', status: '0' },
{ id: 2, kcmc: '大学英语', lx: '停课', jxsj: '2026-2027-1', sksj: '周三 第5-6节', zxkb: '—', jy: '李教员', sqjy: '王教员', jxcd: '1号教学楼-205', dbbc: '二班', zxsj: '2026-09-03', bz: '教员参加教研会议,申请停课顺延', status: '1' },
{ id: 3, kcmc: '计算机基础', lx: '补课', jxsj: '2026-2027-1', sksj: '周五 第7-8节', zxkb: '周六 第1-2节', jy: '王教员', sqjy: '王教员', jxcd: '实验楼-303', dbbc: '三班', zxsj: '2026-09-04', bz: '补上周五因停课落下的课程', status: '1' },
{ id: 4, kcmc: '军事理论', lx: '调课', jxsj: '2026-2027-1', sksj: '周四 第3-4节', zxkb: '周五 第3-4节', jy: '赵教员', sqjy: '赵教员', jxcd: '3号教学楼-110', dbbc: '一班', zxsj: '2026-09-05', bz: '场地临时被占用,调换上课时间', status: '2' },
{ id: 5, kcmc: '高等数学', lx: '补课', jxsj: '2026-2027-1', sksj: '周二 第1-2节', zxkb: '周四 第7-8节', jy: '张教员', sqjy: '张教员', jxcd: '1号教学楼-101', dbbc: '二班', zxsj: '2026-09-06', bz: '补国庆假期调休停上的课程', status: '0' }
]
import {
listScheduleAdjust,
listAdjustableLessons,
getScheduleAdjustDetail,
submitScheduleAdjust,
auditByTeachingOffice,
auditByTeachingDepartment,
auditByAuthority,
receiveByTeachingOffice,
receiveByAuthority,
cancelScheduleAdjust
} from '@/api/teachBusiness/courseRunning'
import { listAllSemester } from '@/api/teachBusiness/semester'
// 状态描述
const STATUS_MAP = {
'0': { label: '待审批', type: 'warning' },
'1': { label: '已批准', type: 'success' },
'2': { label: '已驳回', type: 'danger' }
// 节次 -> 节次区间映射
const JC_SECTION = {
1: '1-2节', 2: '3-4节', 3: '5-6节', 4: '7-8节', 5: '9-10节', 6: '11-12节'
}
// 审批状态:0-未审批,1-已同意,2-已发回,3-已拒绝
const AUDIT_STATUS = {
0: { label: '未审批', type: 'info' },
1: { label: '已同意', type: 'success' },
2: { label: '已发回', type: 'warning' },
3: { label: '已拒绝', type: 'danger' }
}
// 查收状态:0-未查收,1-已查收
const RECEIVE_STATUS = {
0: { label: '未查收', type: 'info' },
1: { label: '已查收', type: 'success' }
}
export default {
name: 'TimetableAdjust',
data() {
const curUserName = (this.$store.state.user && this.$store.state.user.name) || ''
return {
curUserName: curUserName,
loading: false,
submitLoading: false,
allData: [],
tableData: [],
total: 0,
queryParams: {
kcmc: undefined,
dbbc: undefined,
sqjy: undefined,
status: undefined
nd: undefined,
sqjybh: undefined,
jyspzzt: undefined,
pageNum: 1,
pageSize: 20
},
statusOptions: [
{ label: '待审批', value: '0' },
{ label: '已批准', value: '1' },
{ label: '已驳回', value: '2' }
// 年度下拉数据来自 /semester/all(真实后端数据)
yearOptions: [],
defaultNd: undefined,
jcOptions: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
auditStatusOptions: [
{ label: '未审批', value: 0 },
{ label: '已同意', value: 1 },
{ label: '已发回', value: 2 },
{ label: '已拒绝', value: 3 }
],
statCards: [],
dialogVisible: false,
// 新增申请
addVisible: false,
addLoading: false,
addForm: {},
addRules: {
nd: [{ required: true, message: '请选择年度', trigger: 'change' }],
sskcbbh: [{ required: true, message: '请输入课次编号', trigger: 'blur' }],
sy: [{ required: true, message: '请输入调课事由', trigger: 'blur' }],
rq: [{ required: true, message: '请选择拟调整日期', trigger: 'change' }],
jc: [{ required: true, message: '请选择拟调整节次', trigger: 'change' }],
sqjybh: [{ required: true, message: '请输入申请教员编号', trigger: 'blur' }]
},
// 可供调课课次
lessonDialogVisible: false,
lessonLoading: false,
lessonOptions: [],
// 审批
auditVisible: false,
auditLoading: false,
auditTitle: '审批',
auditForm: { ssdksqbh: '', auditRole: 'jys', spzt: 1, fhyj: '', sprbh: '', jgbh: '' },
// 查收
receiveVisible: false,
receiveLoading: false,
receiveTitle: '查收',
receiveForm: { ssdksqbh: '', type: 'jys', csrbh: '', jgbh: '' },
// 详情
detailVisible: false,
detailTarget: null,
form: {},
rules: {
kcmc: [{ required: true, message: '请输入课程名称', trigger: 'blur' }],
lx: [{ required: true, message: '请选择申请类型', trigger: 'change' }],
dbbc: [{ required: true, message: '请输入队别班次', trigger: 'blur' }],
bz: [{ required: true, message: '请输入申请理由', trigger: 'blur' }]
}
}
},
computed: {
filteredData() {
const q = this.queryParams
return this.allData.filter(item => {
if (q.kcmc && !(item.kcmc || '').includes(q.kcmc)) return false
if (q.dbbc && !(item.dbbc || '').includes(q.dbbc)) return false
if (q.sqjy && !(item.sqjy || '').includes(q.sqjy)) return false
if (q.status !== undefined && q.status !== '' && item.status !== q.status) return false
return true
})
detailLoading: false,
detailData: null
}
},
created() {
this.loadData()
this.loadYearOptions().then(() => this.loadList())
},
methods: {
typeTag(lx) {
const map = { '调课': 'warning', '停课': 'danger', '补课': 'success' }
return map[lx] || 'info'
/* ---------- 年度下拉(数据来自 /semester/all) ---------- */
loadYearOptions() {
return listAllSemester().then(response => {
const list = response.data || []
// 去重并按年度倒序
const map = {}
list.forEach(item => {
if (item.nd !== null && item.nd !== undefined && item.nd !== '') map[item.nd] = item
})
const arr = Object.keys(map).sort((a, b) => String(b).localeCompare(String(a)))
this.yearOptions = arr
// 默认年度:优先当前学期(dqxq=true),否则取最新年度
const current = list.find(item => item.dqxq === true)
this.defaultNd = (current && current.nd) || arr[0]
if (!this.queryParams.nd) this.queryParams.nd = this.defaultNd
return arr
}).catch(() => {
this.yearOptions = []
this.defaultNd = undefined
return []
})
},
statusTag(status) {
return STATUS_MAP[status] || { label: status, type: 'info' }
/* ---------- 通用格式化 ---------- */
fmtDateTime(v) {
if (!v) return '-'
return String(v).replace('T', ' ').substring(0, 16)
},
loadData() {
fmtRqJc(rq, jc) {
if (!rq) return '-'
const date = String(rq).substring(0, 10)
const sec = jc === null || jc === undefined || jc === '' ? '第?节' : (JC_SECTION[jc] || '第' + jc + '节')
return date + ' ' + sec
},
jcLabel(n) {
return JC_SECTION[n] || ('第' + n + '节')
},
auditStatusTag(v) {
return AUDIT_STATUS[v] || { label: '-', type: 'info' }
},
receiveStatusTag(v) {
return RECEIVE_STATUS[v] || { label: '-', type: 'info' }
},
/* ---------- 列表查询 ---------- */
loadList() {
const params = {
pageNum: this.queryParams.pageNum,
pageSize: this.queryParams.pageSize
}
if (this.queryParams.nd) params.nd = this.queryParams.nd
if (this.queryParams.sqjybh) params.sqjybh = this.queryParams.sqjybh
if (this.queryParams.jyspzzt !== undefined && this.queryParams.jyspzzt !== '') {
params.jyspzzt = this.queryParams.jyspzzt
}
this.loading = true
// 模拟接口延迟,后续替换为真实接口
setTimeout(() => {
this.allData = MOCK_LIST.map((item, index) => ({ ...item, xh: index + 1 }))
listScheduleAdjust(params).then(response => {
const data = response.data || {}
this.tableData = data.records || []
this.total = data.total || 0
this.refreshStatCards()
this.loading = false
}, 200)
},
refreshStatCards() {
const total = this.allData.length
const pending = this.allData.filter(i => i.status === '0').length
const approved = this.allData.filter(i => i.status === '1').length
const rejected = this.allData.filter(i => i.status === '2').length
this.statCards = [
{ title: '申请总数', value: total, gradient: 'linear-gradient(135deg, #00875a 0%, #00b877 100%)' },
{ title: '待审批', value: pending, gradient: 'linear-gradient(135deg, #e6a23c 0%, #f8b04c 100%)' },
{ title: '已批准', value: approved, gradient: 'linear-gradient(135deg, #409eff 0%, #66b1ff 100%)' },
{ title: '已驳回', value: rejected, gradient: 'linear-gradient(135deg, #f56c6c 0%, #f78989 100%)' }
]
},
handleStatClick(card) {
const statusMap = { '待审批': '0', '已批准': '1', '已驳回': '2' }
if (statusMap[card.title]) {
this.queryParams.status = this.queryParams.status === statusMap[card.title] ? undefined : statusMap[card.title]
this.$message.info(`已按「${card.title}」筛选`)
}
}).catch(() => {
this.tableData = []
this.total = 0
this.refreshStatCards()
this.loading = false
})
},
handleQuery() {
this.$message.success('查询完成')
this.queryParams.pageNum = 1
this.loadList()
},
resetQuery() {
this.queryParams = {
kcmc: undefined,
dbbc: undefined,
sqjy: undefined,
status: undefined
nd: this.defaultNd,
sqjybh: undefined,
jyspzzt: undefined,
pageNum: 1,
pageSize: 20
}
this.loadList()
},
refreshStatCards() {
const list = this.tableData
this.statCards = [
{ title: '本页申请', value: list.length, gradient: 'linear-gradient(135deg, #00875a 0%, #00b877 100%)', filter: undefined },
{ title: '教研室待审批', value: list.filter(i => i.jyspzzt === 0).length, gradient: 'linear-gradient(135deg, #e6a23c 0%, #f8b04c 100%)', filter: 0 },
{ title: '教研室已同意', value: list.filter(i => i.jyspzzt === 1).length, gradient: 'linear-gradient(135deg, #409eff 0%, #66b1ff 100%)', filter: 1 },
{ title: '教研室已拒绝', value: list.filter(i => i.jyspzzt === 3).length, gradient: 'linear-gradient(135deg, #f56c6c 0%, #f78989 100%)', filter: 3 }
]
},
handleStatClick(card) {
if (card.filter === undefined) return
this.queryParams.jyspzzt = this.queryParams.jyspzzt === card.filter ? undefined : card.filter
this.handleQuery()
},
/* ---------- 新增申请 ---------- */
openAdd() {
this.form = { kcmc: '', lx: '调课', dbbc: '', sksj: '', zxkb: '', bz: '' }
this.$nextTick(() => this.$refs.form && this.$refs.form.clearValidate())
this.dialogVisible = true
this.addForm = {
nd: this.defaultNd || this.yearOptions[0],
sskcbbh: '',
sy: '',
rq: '',
jc: 1,
sqjybh: this.curUserName,
jxnr: '',
jxyd: '',
jxff: '',
jxbzbz: '',
ycxx: '',
bz: ''
}
this.$nextTick(() => this.$refs.addForm && this.$refs.addForm.clearValidate())
this.addVisible = true
},
submitForm() {
this.$refs.form.validate(valid => {
if (!valid) return
this.submitLoading = true
setTimeout(() => {
const nextId = this.allData.length ? Math.max(...this.allData.map(i => i.id)) + 1 : 1
this.allData.unshift({
id: nextId,
kcmc: this.form.kcmc,
lx: this.form.lx,
jxsj: '2026-2027-1',
sksj: this.form.sksj || '待定',
zxkb: this.form.lx === '停课' ? '—' : (this.form.zxkb || '待定'),
jy: '-',
sqjy: this.form.sqjy || '-',
jxcd: '-',
dbbc: this.form.dbbc,
zxsj: this.getNow(),
bz: this.form.bz,
status: '0'
})
this.refreshStatCards()
this.submitLoading = false
this.dialogVisible = false
this.$message.success('申请已提交')
}, 300)
loadAdjustableLessons() {
if (!this.addForm.nd) {
this.$message.warning('请先选择年度')
return
}
this.lessonLoading = true
listAdjustableLessons({ nd: this.addForm.nd }).then(response => {
this.lessonOptions = response.data || []
this.lessonLoading = false
this.lessonDialogVisible = true
}).catch(() => {
this.lessonLoading = false
this.$message.info('后端暂未提供可供调课课次接口,请手动填写课次编号')
})
},
getNow() {
const d = new Date()
const p = n => (n < 10 ? '0' + n : '' + n)
return d.getFullYear() + '-' + p(d.getMonth() + 1) + '-' + p(d.getDate())
pickLesson(row) {
this.addForm.sskcbbh = row.sskcbbh
this.lessonDialogVisible = false
this.$message.success('已选择课次:' + (row.kcmc || row.sskcbbh))
},
approve(row) {
row.status = '1'
this.refreshStatCards()
this.$message.success('已批准该申请')
submitAdd() {
this.$refs.addForm.validate(valid => {
if (!valid) return
const payload = {
sskcbbh: this.addForm.sskcbbh,
sy: this.addForm.sy,
rq: this.addForm.rq + 'T00:00:00',
jc: this.addForm.jc,
nd: this.addForm.nd,
sqjybh: this.addForm.sqjybh
}
;['jxnr', 'jxyd', 'jxff', 'jxbzbz', 'ycxx', 'bz'].forEach(key => {
if (this.addForm[key]) payload[key] = this.addForm[key]
})
this.addLoading = true
submitScheduleAdjust(payload).then(() => {
this.addLoading = false
this.addVisible = false
this.$message.success('申请提交成功')
this.loadList()
}).catch(() => {
this.addLoading = false
})
})
},
reject(row) {
row.status = '2'
this.refreshStatCards()
this.$message.warning('已驳回该申请')
/* ---------- 审批 ---------- */
showAuditMenu(row) {
return row.sczt === 0 && (row.jyspzzt === 0 || row.jyspzzt === 1)
},
removeRow(row) {
this.$confirm('确认删除该申请?', '提示', { type: 'warning' })
openAudit(row, role) {
const titleMap = { jys: '教研室审批', jxx: '教学系审批', jg: '机关审批' }
this.auditTitle = titleMap[role] || '审批'
this.auditForm = {
ssdksqbh: row.ssdksqbh,
auditRole: role,
spzt: 1,
fhyj: '',
sprbh: this.curUserName,
jgbh: ''
}
this.auditVisible = true
},
submitAudit() {
const f = this.auditForm
if (!f.sprbh) {
this.$message.warning('请填写审批人编号')
return
}
if (f.auditRole === 'jg' && !f.jgbh) {
this.$message.warning('请填写机关编号')
return
}
const payload = {
ssdksqbh: f.ssdksqbh,
auditRole: f.auditRole,
spzt: f.spzt,
fhyj: f.fhyj,
sprbh: f.sprbh
}
if (f.jgbh) payload.jgbh = f.jgbh
const apiMap = {
jys: auditByTeachingOffice,
jxx: auditByTeachingDepartment,
jg: auditByAuthority
}
this.auditLoading = true
apiMap[f.auditRole](payload).then(() => {
this.auditLoading = false
this.auditVisible = false
this.$message.success('审批成功')
this.loadList()
}).catch(() => {
this.auditLoading = false
})
},
/* ---------- 查收 ---------- */
showReceiveMenu(row) {
return row.sczt === 0 && row.jyspzzt === 1
},
openReceive(row, type) {
this.receiveTitle = type === 'jys' ? '教研室查收' : '机关查收'
this.receiveForm = {
ssdksqbh: row.ssdksqbh,
type: type,
csrbh: this.curUserName,
jgbh: ''
}
this.receiveVisible = true
},
submitReceive() {
const f = this.receiveForm
if (!f.csrbh) {
this.$message.warning('请填写查收人编号')
return
}
if (f.type === 'jg' && !f.jgbh) {
this.$message.warning('请填写机关编号')
return
}
const params = { ssdksqbh: f.ssdksqbh, csrbh: f.csrbh }
if (f.jgbh) params.jgbh = f.jgbh
const api = f.type === 'jys' ? receiveByTeachingOffice : receiveByAuthority
this.receiveLoading = true
api(params).then(() => {
this.receiveLoading = false
this.receiveVisible = false
this.$message.success('查收成功')
this.loadList()
}).catch(() => {
this.receiveLoading = false
})
},
/* ---------- 撤销 ---------- */
handleCancel(row) {
this.$confirm('确认撤销该调课申请?撤销后课次将恢复原时间。', '提示', { type: 'warning' })
.then(() => {
this.allData = this.allData.filter(i => i.id !== row.id)
this.refreshStatCards()
this.$message.success('删除成功')
cancelScheduleAdjust({ ssdksqbh: row.ssdksqbh, sqjybh: row.sqjybh }).then(() => {
this.$message.success('撤销成功')
this.loadList()
}).catch(() => {})
})
.catch(() => {})
},
/* ---------- 详情 ---------- */
openDetail(row) {
this.detailTarget = row
this.detailVisible = true
this.detailLoading = true
this.detailData = null
getScheduleAdjustDetail(row.ssdksqbh).then(response => {
this.detailData = response.data || {}
this.detailLoading = false
}).catch(() => {
this.detailData = null
this.detailLoading = false
})
}
}
}
@@ -405,6 +783,30 @@ export default {
color: #f78989;
}
}
.ops-cell {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
gap: 0 4px;
}
.pick-lesson-btn {
margin-top: 6px;
width: 100%;
}
.sub-section {
margin-top: 16px;
.sub-title {
font-size: 14px;
font-weight: 700;
color: #303133;
margin-bottom: 8px;
}
}
}
@media (max-width: 992px) {
@@ -412,4 +814,4 @@ export default {
grid-template-columns: repeat(2, 1fr);
}
}
</style>
</style>
@@ -5,9 +5,16 @@
<div class="section-header">
<div>
<div class="section-title">教学资源冲突检查</div>
<div class="section-desc">2026年秋季学期教学资源冲突检查,可单独检查或执行全部检查</div>
<div class="section-desc">当前检查年度:{{ nd ? nd + ' 年' : '未选择' }},可单独检查或执行全部检查</div>
</div>
<div class="header-actions">
<el-form inline class="toolbar-form" @submit.native.prevent>
<el-form-item label="年度" class="year-item">
<el-select v-model="nd" placeholder="请选择年度" style="width: 130px" @change="handleNdChange">
<el-option v-for="y in yearOptions" :key="y" :label="y + ' 年'" :value="y" />
</el-select>
</el-form-item>
</el-form>
<el-button size="mini" :disabled="loading" @click="handleReset">重置</el-button>
<el-button type="primary" size="mini" :loading="loading" @click="handleCheckAll">执行全部检查</el-button>
</div>
@@ -20,7 +27,7 @@
:key="item.key"
class="check-item-card"
:class="{ active: activeKey === item.key }"
@click="activeKey = item.checked ? item.key : activeKey"
@click="selectCard(item)"
>
<div class="check-item-header">
<span class="check-item-name">{{ item.label }}</span>
@@ -45,145 +52,228 @@
冲突明细
<span v-if="activeKey" class="active-label">— {{ activeLabel }}</span>
</div>
<span v-if="activeKey" class="detail-count">共 {{ activeDetails.length }} 条记录</span>
<span v-if="activeKey" class="detail-count">共 {{ detailTotal }} 条记录</span>
</div>
<el-table v-if="activeKey" :data="activeDetails" border stripe size="mini" max-height="500">
<el-table-column type="index" label="序号" width="55" align="center" />
<el-table-column label="冲突号" width="80" align="center">
<template slot-scope="scope">
<el-tag type="danger" size="mini">#{{ Math.floor(scope.$index / 2) + 1 }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="kcmc" label="课程" min-width="130" show-overflow-tooltip />
<el-table-column label="日期" width="100">
<template slot-scope="scope">{{ formatDateStr(scope.row.rq) }}</template>
</el-table-column>
<el-table-column prop="jc" label="节次" width="70" align="center" />
<el-table-column prop="bc" label="班次" width="130" show-overflow-tooltip />
<el-table-column prop="skjy" label="教员" width="90" />
<el-table-column prop="jxcd" label="场地" width="130" show-overflow-tooltip />
<el-table-column prop="zrdw" label="责任单位" width="100" />
</el-table>
<template v-if="activeKey">
<el-table v-loading="detailLoading" :data="activeDetails" border stripe size="mini" max-height="500">
<el-table-column type="index" label="序号" width="55" align="center" />
<el-table-column label="冲突号" width="90" align="center" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row.conflictNo || '-' }}</template>
</el-table-column>
<el-table-column prop="courseNames" label="课程" min-width="150" show-overflow-tooltip header-align="center" />
<el-table-column label="日期" width="100" align="center">
<template slot-scope="scope">{{ fmtDate(scope.row.rq) }}</template>
</el-table-column>
<el-table-column prop="jc" label="节次" width="70" align="center" />
<el-table-column prop="xydNames" label="班次" min-width="140" show-overflow-tooltip header-align="center" />
<el-table-column prop="teacherNames" label="教员" min-width="120" show-overflow-tooltip header-align="center" />
<el-table-column prop="classroomNames" label="场地" min-width="140" show-overflow-tooltip header-align="center" />
<el-table-column prop="responsibleDept" label="责任单位" min-width="110" show-overflow-tooltip header-align="center" />
</el-table>
<el-pagination
v-if="detailTotal > 0"
class="detail-pagination"
background
layout="total, prev, pager, next"
:current-page="detailQuery.pageNum"
:page-size="detailQuery.pageSize"
:total="detailTotal"
@current-change="handlePageChange"
/>
<el-empty v-if="detailTotal === 0 && !detailLoading" description="暂无冲突明细" />
</template>
<el-empty v-else v-show="!loading" description="点击上方卡片「检查」按钮,在此查看冲突明细" />
</div>
</div>
</template>
<script>
// 教学计划静态示例数据(待对接 getTeachingPlanList 接口)
const MOCK_PLANS = [
{ kcmc: '高等数学', rq: '2026-09-01', jc: 1, bc: '一班', skjy: '张教员', jxcd: '1号教学楼-101', zrdw: '基础部' },
{ kcmc: '高等数学', rq: '2026-09-01', jc: 1, bc: '一班', skjy: '张教员', jxcd: '1号教学楼-101', zrdw: '基础部' },
{ kcmc: '大学英语', rq: '2026-09-01', jc: 1, bc: '二班', skjy: '李教员', jxcd: '1号教学楼-101', zrdw: '基础部' },
{ kcmc: '大学英语', rq: '2026-09-02', jc: 3, bc: '二班', skjy: '李教员', jxcd: '2号教学楼-205', zrdw: '基础部' },
{ kcmc: '计算机基础', rq: '2026-09-01', jc: 1, bc: '三班', skjy: '王教员', jxcd: '实验楼-303', zrdw: '信息工程系' },
{ kcmc: '军事理论', rq: '2026-09-02', jc: 3, bc: '一班', skjy: '赵教员', jxcd: '3号教学楼-110', zrdw: '政工系' }
]
import { checkConflict, checkAllConflict, resetConflict, getConflictDetails } from '@/api/teachBusiness/timetableConflict'
import { listAllSemester } from '@/api/teachBusiness/semester'
function formatDateStr(rq) {
return (rq || '').substring(0, 10)
}
// 四类检查卡片定义(标题/描述与后端 TimetableConflictDimension 枚举一致,作为页面布局常量;
// 检查状态与冲突数完全来自后端接口)
const CARD_DEFS = [
{ key: 'TEAM_CONFLICT', label: '教学班时间冲突检查', desc: '同一教学班次在同一时间段被安排多门课程' },
{ key: 'ELECTIVE_REQUIRED_CONFLICT', label: '教学班必修与选修时间冲突检查', desc: '教学班次必修课与选修课时间重叠' },
{ key: 'TEACHER_CONFLICT', label: '教员时间冲突检查', desc: '同一教员在同一时间段被安排多门课程' },
{ key: 'CLASSROOM_CONFLICT', label: '教室时间冲突检查', desc: '同一教室在同一时间段被多门课程占用' }
]
export default {
name: 'TimetableConflict',
data() {
return {
loading: false,
allRecords: [],
detailLoading: false,
// 年度(数据来自 /semester/all,真实后端数据)
nd: undefined,
yearOptions: [],
conflictItems: CARD_DEFS.map(c => ({ ...c, count: 0, checked: false })),
activeKey: '',
conflictItems: [
{ key: 'jxbssj', label: '教学班次时间冲突检查', desc: '同一教学班次在同一时间段被安排多门课程', count: 0, checked: false, details: [] },
{ key: 'jxbskcsj', label: '教学班次必修与选修时间冲突检查', desc: '教学班次必修课与选修课时间重叠', count: 0, checked: false, details: [] },
{ key: 'jysj', label: '教员时间冲突检查', desc: '同一教员在同一时间段被安排多门课程', count: 0, checked: false, details: [] },
{ key: 'jssj', label: '教室时间冲突检查', desc: '同一教室在同一时间段被多门课程占用', count: 0, checked: false, details: [] }
]
activeDetails: [],
detailTotal: 0,
detailQuery: { pageNum: 1, pageSize: 20 }
}
},
computed: {
activeLabel() {
const item = this.conflictItems.find(i => i.key === this.activeKey)
return item ? item.label : ''
},
// 当前选中检查项的展开明细(打平所有冲突记录)
activeDetails() {
const item = this.conflictItems.find(i => i.key === this.activeKey)
if (!item) return []
const flat = []
item.details.forEach(d => {
d.items.forEach(it => flat.push(it))
})
return flat
}
},
created() {
// 初始化演示数据(模拟接口返回)
this.allRecords = MOCK_PLANS.map((item, index) => ({ ...item, xh: index + 1 }))
this.loadYearOptions()
},
methods: {
/* ---------- 年度下拉(数据来自 /semester/all) ---------- */
loadYearOptions() {
return listAllSemester().then(response => {
const list = response.data || []
// 去重并按年度倒序
const map = {}
list.forEach(item => {
if (item.nd !== null && item.nd !== undefined && item.nd !== '') map[item.nd] = item
})
const arr = Object.keys(map).sort((a, b) => String(b).localeCompare(String(a)))
this.yearOptions = arr
// 默认年度:优先当前学期(dqxq=true),否则取最新年度
const current = list.find(item => item.dqxq === true)
this.nd = (current && current.nd) || arr[0]
return arr
}).catch(() => {
this.yearOptions = []
this.nd = undefined
return []
})
},
fmtDate(rq) {
return (rq || '').substring(0, 10)
},
getStatus(item) {
if (!item.checked) return { type: 'info', text: '未检查' }
return item.count > 0
? { type: 'danger', text: '存在 ' + item.count + ' 处冲突' }
: { type: 'success', text: '无冲突' }
},
/** 通用冲突检测:按分组字段找 > 1 的记录 */
detectConflicts(records, groupFields) {
const map = {}
records.forEach(item => {
const key = groupFields.map(f => f + '=' + (item[f] !== undefined && item[f] !== null ? item[f] : '')).join('|') +
'|rq=' + formatDateStr(item.rq) + '|jc=' + (item.jc !== undefined && item.jc !== null ? item.jc : '')
if (!map[key]) map[key] = []
map[key].push(item)
})
return Object.keys(map)
.filter(k => map[k].length > 1)
.map(k => ({ key: k, items: map[k] }))
.sort((a, b) => a.key.localeCompare(b.key))
/* 用后端返回的单卡片结果同步对应卡片状态 */
applyCard(card) {
if (!card) return
const item = this.conflictItems.find(i => i.key === card.dimensionCode)
if (!item) return
item.checked = card.checked
item.count = card.conflictCount
},
applySummary(summary) {
if (!summary || !summary.cards) return
summary.cards.forEach(card => this.applyCard(card))
},
/* ---------- 单个检查 ---------- */
handleCheck(item) {
if (this.allRecords.length === 0) return
let details = []
switch (item.key) {
case 'jxbssj':
details = this.detectConflicts(this.allRecords, ['bc'])
break
case 'jxbskcsj':
details = this.detectConflicts(this.allRecords, ['bc'])
break
case 'jysj':
details = this.detectConflicts(this.allRecords, ['skjy'])
break
case 'jssj':
details = this.detectConflicts(this.allRecords, ['jxcd'])
break
}
item.details = details
item.count = details.length
item.checked = true
this.activeKey = item.key
if (item.count > 0) {
this.$message.warning('「' + item.label + '」发现 ' + item.count + ' 处冲突')
} else {
this.$message.success('「' + item.label + '」未发现冲突')
if (!this.nd) {
this.$message.warning('请先选择年度')
return
}
this.loading = true
checkConflict({ nd: this.nd, dimensionCode: item.key }).then(response => {
this.applyCard(response.data)
this.activeKey = item.key
this.detailQuery.pageNum = 1
this.loadDetails()
if (item.count > 0) {
this.$message.warning('「' + item.label + '」检查完成,发现 ' + item.count + ' 处冲突')
} else {
this.$message.success('「' + item.label + '」检查完成,未发现冲突')
}
}).finally(() => {
this.loading = false
})
},
/* ---------- 全部检查 ---------- */
handleCheckAll() {
this.conflictItems.forEach(item => this.handleCheck(item))
const firstConflict = this.conflictItems.find(i => i.count > 0)
if (firstConflict) {
this.activeKey = firstConflict.key
if (!this.nd) {
this.$message.warning('请先选择年度')
return
}
const total = this.conflictItems.reduce((sum, item) => sum + item.count, 0)
this.$message.success('全部检查完成,共发现 ' + total + ' 处冲突')
this.loading = true
checkAllConflict({ nd: this.nd }).then(response => {
const summary = response.data
this.applySummary(summary)
const total = summary ? summary.totalConflictCount : 0
this.$message.success('全部检查完成,共发现 ' + total + ' 处冲突')
const firstConflict = this.conflictItems.find(i => i.checked && i.count > 0)
const firstChecked = this.conflictItems.find(i => i.checked)
this.activeKey = firstConflict ? firstConflict.key : (firstChecked ? firstChecked.key : '')
this.detailQuery.pageNum = 1
if (this.activeKey) {
this.loadDetails()
} else {
this.activeDetails = []
this.detailTotal = 0
}
}).finally(() => {
this.loading = false
})
},
/* ---------- 重置 ---------- */
handleReset() {
this.conflictItems.forEach(item => {
item.count = 0
item.checked = false
item.details = []
if (!this.nd) {
this.$message.warning('请先选择年度')
return
}
resetConflict({ nd: this.nd }).then(response => {
this.applySummary(response.data)
this.activeKey = ''
this.activeDetails = []
this.detailTotal = 0
this.$message.success('检查结果已重置')
})
},
/* 点击卡片:仅已检查的卡片可查看其明细 */
selectCard(item) {
if (!item.checked) return
this.activeKey = item.key
this.detailQuery.pageNum = 1
this.loadDetails()
},
/* ---------- 冲突明细 ---------- */
loadDetails() {
if (!this.activeKey) {
this.activeDetails = []
this.detailTotal = 0
return
}
this.detailLoading = true
getConflictDetails({
nd: this.nd,
dimensionCode: this.activeKey,
pageNum: this.detailQuery.pageNum,
pageSize: this.detailQuery.pageSize
}).then(response => {
const page = response.data || {}
this.activeDetails = page.records || []
this.detailTotal = page.total || 0
}).catch(() => {
this.activeDetails = []
this.detailTotal = 0
}).finally(() => {
this.detailLoading = false
})
},
handlePageChange(page) {
this.detailQuery.pageNum = page
this.loadDetails()
},
/* 切换年度:该年度检查状态未知,重置为未检查 */
handleNdChange() {
this.conflictItems.forEach(i => {
i.checked = false
i.count = 0
})
this.activeKey = ''
this.$message.info('已重置检查结果')
this.activeDetails = []
this.detailTotal = 0
this.detailQuery.pageNum = 1
}
}
}
@@ -216,6 +306,14 @@ export default {
display: flex;
align-items: center;
gap: 12px;
.toolbar-form {
margin-right: 4px;
.year-item {
margin-bottom: 0;
}
}
}
}
@@ -310,5 +408,10 @@ export default {
font-size: 13px;
color: #909399;
}
.detail-pagination {
margin-top: 12px;
text-align: right;
}
}
</style>
@@ -2,100 +2,46 @@
<div class="app-container training-plan-page">
<!-- ==================== 1. 查询条件区域 ==================== -->
<el-card shadow="never" class="search-card">
<el-form :model="searchForm" label-width="120px" class="search-form">
<el-row :gutter="32">
<!-- 左栏 -->
<el-col :xs="24" :md="12">
<el-form :model="searchForm" label-width="100px" class="search-form" @submit.native.prevent>
<el-row :gutter="24">
<el-col :xs="24" :md="8">
<el-form-item label="专业名称">
<el-input v-model="searchForm.zymc" placeholder="请输入专业名称" clearable />
</el-form-item>
<el-form-item label="专业方向">
<el-input v-model="searchForm.zyfx" placeholder="请输入专业方向" clearable />
</el-form-item>
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.pxlxEnabled">培训类型</el-checkbox>
</template>
<el-select v-model="searchForm.pxlx" placeholder="请选择培训类型" clearable class="w-full"
:disabled="!searchForm.pxlxEnabled">
<el-option v-for="item in pxlxOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.pxccEnabled">培训层次</el-checkbox>
</template>
<el-select v-model="searchForm.pxcc" placeholder="请选择培训层次" clearable class="w-full"
:disabled="!searchForm.pxccEnabled">
<el-option v-for="item in pxccOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item label="自定义分类">
<el-input v-model="searchForm.zdyfl" placeholder="请输入自定义分类" clearable />
</el-form-item>
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.zgzyEnabled">主干专业</el-checkbox>
</template>
<el-select v-model="searchForm.zgzy" placeholder="请选择" clearable class="w-full"
:disabled="!searchForm.zgzyEnabled">
<el-option v-for="item in zgzyOptions" :key="item" :label="item" :value="item" />
</el-select>
<el-input v-model="searchForm.zymc" placeholder="请输入专业名称(模糊)" clearable />
</el-form-item>
</el-col>
<!-- 右栏 -->
<el-col :xs="24" :md="12">
<el-col :xs="24" :md="8">
<el-form-item label="专业代码">
<el-input v-model="searchForm.zydm" placeholder="请输入专业代码" clearable />
<el-input v-model="searchForm.zydm" placeholder="请输入专业代码(模糊)" clearable />
</el-form-item>
<el-form-item label="学年制">
<el-input v-model="searchForm.xnz" placeholder="请输入学年制" clearable />
</el-col>
<el-col :xs="24" :md="8">
<el-form-item label="培训类型">
<el-input v-model="searchForm.pxlx" placeholder="请输入培训类型(精确)" clearable />
</el-form-item>
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.pxlx2Enabled">培训类型2</el-checkbox>
</template>
<el-select v-model="searchForm.pxlx2" placeholder="请选择培训类型2" clearable class="w-full"
:disabled="!searchForm.pxlx2Enabled">
<el-option v-for="item in pxlx2Options" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.qyztEnabled">启用状态</el-checkbox>
</template>
<el-radio-group v-model="searchForm.qyzt" :disabled="!searchForm.qyztEnabled">
<el-radio :label="'启用'">启用</el-radio>
<el-radio :label="'停用'">停用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="大纲版本">
<el-input v-model="searchForm.zybb" placeholder="请输入大纲版本" clearable />
</el-form-item>
<el-form-item label="培养目标">
<el-input v-model="searchForm.pymb" placeholder="请输入培养目标" clearable />
</el-col>
<el-col :xs="24" :md="8">
<el-form-item label="培训层次">
<el-input v-model="searchForm.pxcc" placeholder="请输入培训层次(精确)" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :md="8">
<div class="search-actions">
<el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
<el-button icon="el-icon-refresh" @click="handleReset">重置</el-button>
</div>
</el-col>
</el-row>
<div class="search-footer">
<div class="notice">注意:勾选"选择框"表示启用该项对应的查询条件。</div>
<div class="search-actions">
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button @click="handleReset">重置</el-button>
</div>
</div>
</el-form>
</el-card>
<!-- ==================== 2. 操作与上传区域 ==================== -->
<el-card shadow="never" class="action-card">
<div class="action-row">
<el-button type="primary" icon="el-icon-plus" @click="handleOpenAdd">新建</el-button>
<el-button type="primary" icon="el-icon-plus" @click="handleAdd">新建</el-button>
<el-button icon="el-icon-download" @click="handleDownload">下载</el-button>
</div>
<div class="action-row">
<el-button type="primary" @click="handleTemplateDownload">【人才培养方案目录模板】下载</el-button>
<el-button icon="el-icon-download" @click="handleTemplateDownload">【人才培养方案目录模板】下载</el-button>
</div>
<div class="upload-row">
<div class="upload-left">
@@ -114,237 +60,387 @@
<!-- ==================== 3. 数据表格区域 ==================== -->
<el-card shadow="never" class="table-card">
<el-table v-loading="loading" :data="tableData" stripe border highlight-current-row
@selection-change="handleSelectionChange">
<el-table-column type="selection" width="40" align="center" />
<el-table-column prop="jxgljg" label="教学管理机构" show-overflow-tooltip />
<el-table-column prop="xkzyxx" label="学科专业信息" show-overflow-tooltip />
<el-table-column prop="rpyx" label="人培信息" width="180" show-overflow-tooltip />
<el-table-column prop="fl" label="分类" align="center" />
<el-table-column prop="xylb" label="学员类别" align="center" />
<el-table-column prop="xnz" label="学年制" align="center" />
<el-table-column prop="xsfb" label="学时分布" align="center" />
<el-table-column prop="zt" label="状态" align="center" />
<el-table-column prop="wjzl" label="文件资料" align="center" />
<el-table-column label="操作" align="center" fixed="right" width="140">
<div class="list-header">
<div class="list-title">人才培养方案列表</div>
</div>
<el-table v-loading="loading" :data="tableData" border stripe highlight-current-row>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="zydh" label="专业代号" min-width="120" show-overflow-tooltip header-align="center" />
<el-table-column prop="zymc" label="专业名称" min-width="140" show-overflow-tooltip header-align="center" />
<el-table-column prop="zyfx" label="专业方向" min-width="120" show-overflow-tooltip header-align="center" />
<el-table-column prop="zydm" label="专业代码" min-width="110" show-overflow-tooltip header-align="center" />
<el-table-column prop="pxlx" label="培训类型" min-width="110" show-overflow-tooltip header-align="center" />
<el-table-column prop="pxcc" label="培训层次" min-width="120" show-overflow-tooltip header-align="center" />
<el-table-column prop="pxlx2" label="培训类型2" min-width="110" show-overflow-tooltip header-align="center" />
<el-table-column prop="xylb" label="学员类别" min-width="100" show-overflow-tooltip header-align="center" />
<el-table-column prop="xnz" label="学年制" min-width="80" show-overflow-tooltip header-align="center" />
<el-table-column prop="xqs" label="学期数" width="80" align="center" />
<el-table-column prop="zybb" label="专业版本" min-width="100" show-overflow-tooltip header-align="center" />
<el-table-column label="主干专业" width="90" align="center">
<template slot-scope="scope">{{ fmtYesNo(scope.row.zgzy) }}</template>
</el-table-column>
<el-table-column label="状态" width="80" align="center">
<template slot-scope="scope">
<el-button type="text" @click="handleEdit(scope.row)">编辑</el-button>
<el-button type="text" style="color: #f56c6c" :disabled="scope.row.ty === 1 || scope.row.zt === '停用'"
@click="handleDisable(scope.row)">
停用
</el-button>
<el-tag :type="isTrue(scope.row.ty) ? 'danger' : 'success'" size="mini">
{{ isTrue(scope.row.ty) ? '停用' : '启用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="启用时间" width="150" align="center">
<template slot-scope="scope">{{ fmtDateTime(scope.row.qysj) }}</template>
</el-table-column>
<el-table-column label="停用时间" width="150" align="center">
<template slot-scope="scope">{{ fmtDateTime(scope.row.tysj) }}</template>
</el-table-column>
<el-table-column label="操作" width="180" align="center" fixed="right">
<template slot-scope="scope">
<el-button type="text" size="mini" icon="el-icon-view" @click="handleDetail(scope.row)">详情</el-button>
<el-button type="text" size="mini" icon="el-icon-edit" @click="handleEdit(scope.row)">编辑</el-button>
<el-button type="text" size="mini" icon="el-icon-circle-close" class="danger-text-btn"
:disabled="isTrue(scope.row.ty)" @click="handleDisable(scope.row)">停用</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
class="pagination"
background
layout="total, sizes, prev, pager, next, jumper"
:current-page="pageNum"
:page-size="pageSize"
:total="total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
class="pagination-wrapper"
@current-change="handlePageChange"
@size-change="handleSizeChange"
@current-change="handlePageChange"
/>
</el-card>
<!-- 新增/编辑弹窗 -->
<el-dialog :visible.sync="dialogVisible" :title="dialogTitle" width="1000px" :align-center="true"
:destroy-on-close="true">
<div v-loading="editLoading">
<el-form ref="addFormRef" :model="addForm" :rules="rules" label-width="140px" class="add-form">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="专业代号" prop="zydh">
<el-input v-model="addForm.zydh" placeholder="请输入专业代号" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="专业名称" prop="zymc">
<el-input v-model="addForm.zymc" placeholder="请输入专业名称" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="专业方向">
<el-input v-model="addForm.zyfx" placeholder="请输入专业方向" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="专业代码" prop="zydm">
<el-input v-model="addForm.zydm" placeholder="请输入专业代码" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="专业版本">
<el-input v-model="addForm.zybb" placeholder="请输入专业版本" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="培训层次" prop="pxcc">
<el-input v-model="addForm.pxcc" placeholder="硕士研究生/博士研究生/本科" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="培训类型" prop="pxlx">
<el-input v-model="addForm.pxlx" placeholder="学历教育/非学历教育" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="培训类型2">
<el-input v-model="addForm.pxlx2" placeholder="全日制/非全日制" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="学员类别">
<el-input v-model="addForm.xylb" placeholder="请输入学员类别" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="学年制">
<el-input v-model="addForm.xnz" placeholder="两年半/三年/四年" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="学期数">
<el-input-number v-model="addForm.xqs" :min="1" :max="12" class="w-full" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="自定义分类">
<el-input v-model="addForm.zdyfl" placeholder="兵器类/电子信息类" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="教学管理机构编号">
<el-input v-model="addForm.jxgljgbh" placeholder="请输入" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="规范名称">
<el-input v-model="addForm.gfmc" placeholder="请输入规范名称" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="专业备注">
<el-input v-model="addForm.zybz" placeholder="请输入备注" clearable />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="培养目标" prop="pymb">
<el-input v-model="addForm.pymb" type="textarea" :rows="3" placeholder="请输入培养目标" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-dialog :title="dialog.title" :visible.sync="dialog.visible" width="860px" append-to-body
:close-on-click-modal="false">
<el-form ref="trainingForm" :model="dialog.form" :rules="rules" label-width="130px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="专业代号" prop="zydh">
<el-input v-model="dialog.form.zydh" placeholder="请输入专业代号(主键)" :disabled="dialog.isEdit" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="专业名称" prop="zymc">
<el-input v-model="dialog.form.zymc" placeholder="请输入专业名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="专业代码" prop="zydm">
<el-input v-model="dialog.form.zydm" placeholder="请输入专业代码" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="专业方向">
<el-input v-model="dialog.form.zyfx" placeholder="请输入专业方向" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="培训层次" prop="pxcc">
<el-input v-model="dialog.form.pxcc" placeholder="请输入培训层次" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="培训类型" prop="pxlx">
<el-input v-model="dialog.form.pxlx" placeholder="请输入培训类型" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="培训类型2">
<el-input v-model="dialog.form.pxlx2" placeholder="请输入培训类型2" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="学员类别">
<el-input v-model="dialog.form.xylb" placeholder="请输入学员类别" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="学年制">
<el-input v-model="dialog.form.xnz" placeholder="请输入学年制" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="学期数">
<el-input-number v-model="dialog.form.xqs" :min="1" :max="20" controls-position="right" class="w-full" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="专业版本">
<el-input v-model="dialog.form.zybb" placeholder="请输入专业版本" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="自定义分类">
<el-input v-model="dialog.form.zdyfl" placeholder="请输入自定义分类" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="教学管理机构编号">
<el-input v-model="dialog.form.jxgljgbh" placeholder="请输入教学管理机构编号" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="规范名称">
<el-input v-model="dialog.form.gfmc" placeholder="请输入规范名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="专业规范">
<el-input v-model="dialog.form.zygf" placeholder="请输入专业规范" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="简称">
<el-input v-model="dialog.form.jc" placeholder="请输入简称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="主干专业">
<el-radio-group v-model="dialog.form.zgzy">
<el-radio :label="true">是</el-radio>
<el-radio :label="false">否</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="专业备注">
<el-input v-model="dialog.form.zybz" placeholder="请输入专业备注" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="培养目标">
<el-input v-model="dialog.form.pymb" type="textarea" :rows="3" placeholder="请输入培养目标" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialog.visible = false">取 消</el-button>
<el-button type="primary" :loading="dialog.submitting" @click="submitDialog">确 定</el-button>
</div>
</el-dialog>
<!-- 详情弹窗 -->
<el-dialog title="人才培养方案详情" :visible.sync="detail.visible" width="760px" append-to-body
:close-on-click-modal="false">
<div v-loading="detail.loading" class="detail-body">
<el-descriptions :column="2" border>
<el-descriptions-item label="专业代号">{{ fmtVal(detail.data.zydh) }}</el-descriptions-item>
<el-descriptions-item label="专业名称">{{ fmtVal(detail.data.zymc) }}</el-descriptions-item>
<el-descriptions-item label="专业代码">{{ fmtVal(detail.data.zydm) }}</el-descriptions-item>
<el-descriptions-item label="专业方向">{{ fmtVal(detail.data.zyfx) }}</el-descriptions-item>
<el-descriptions-item label="培训类型">{{ fmtVal(detail.data.pxlx) }}</el-descriptions-item>
<el-descriptions-item label="培训层次">{{ fmtVal(detail.data.pxcc) }}</el-descriptions-item>
<el-descriptions-item label="培训类型2">{{ fmtVal(detail.data.pxlx2) }}</el-descriptions-item>
<el-descriptions-item label="学员类别">{{ fmtVal(detail.data.xylb) }}</el-descriptions-item>
<el-descriptions-item label="学年制">{{ fmtVal(detail.data.xnz) }}</el-descriptions-item>
<el-descriptions-item label="学期数">{{ fmtVal(detail.data.xqs) }}</el-descriptions-item>
<el-descriptions-item label="专业版本">{{ fmtVal(detail.data.zybb) }}</el-descriptions-item>
<el-descriptions-item label="主干专业">{{ fmtYesNo(detail.data.zgzy) }}</el-descriptions-item>
<el-descriptions-item label="自定义分类">{{ fmtVal(detail.data.zdyfl) }}</el-descriptions-item>
<el-descriptions-item label="教学管理机构编号">{{ fmtVal(detail.data.jxgljgbh) }}</el-descriptions-item>
<el-descriptions-item label="规范名称">{{ fmtVal(detail.data.gfmc) }}</el-descriptions-item>
<el-descriptions-item label="专业规范">{{ fmtVal(detail.data.zygf) }}</el-descriptions-item>
<el-descriptions-item label="简称">{{ fmtVal(detail.data.jc) }}</el-descriptions-item>
<el-descriptions-item label="专业标识号">{{ fmtVal(detail.data.zybsh) }}</el-descriptions-item>
<el-descriptions-item label="学科专业信息标识号">{{ fmtVal(detail.data.xkzyxxbsh) }}</el-descriptions-item>
<el-descriptions-item label="教学大纲编号">{{ fmtVal(detail.data.jxdgbh) }}</el-descriptions-item>
<el-descriptions-item label="人培编号">{{ fmtVal(detail.data.rpbh) }}</el-descriptions-item>
<el-descriptions-item label="节次类别">{{ fmtVal(detail.data.jclb) }}</el-descriptions-item>
<el-descriptions-item label="系统模式">{{ fmtVal(detail.data.xtms) }}</el-descriptions-item>
<el-descriptions-item label="启用时间">{{ fmtDateTime(detail.data.qysj) }}</el-descriptions-item>
<el-descriptions-item label="停用时间">{{ fmtDateTime(detail.data.tysj) }}</el-descriptions-item>
<el-descriptions-item label="专业备注" :span="2">{{ fmtVal(detail.data.zybz) }}</el-descriptions-item>
<el-descriptions-item label="培养目标" :span="2">{{ fmtVal(detail.data.pymb) }}</el-descriptions-item>
</el-descriptions>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="handleCancel">取消</el-button>
<el-button type="primary" :loading="addLoading" @click="handleSubmit">保存</el-button>
<el-button @click="detail.visible = false">关 闭</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
// 各下拉框选项:数据待后端接口返回后替换
const PXLX_OPTIONS = [] // 培训类型
const PXCC_OPTIONS = [] // 培训层次
const ZGZY_OPTIONS = [] // 主干专业
const PXLX2_OPTIONS = [] // 培训类型2
import {
addTraining,
disableTraining,
updateTraining,
getTraining,
listTraining
} from '@/api/teachBusiness/training'
export default {
name: 'TrainingPlan',
data() {
return {
options: {
pxlx: PXLX_OPTIONS,
pxcc: PXCC_OPTIONS,
zgzy: ZGZY_OPTIONS,
pxlx2: PXLX2_OPTIONS
},
// 查询条件
loading: false,
// 查询条件(仅传后端 ZYBMapper 支持的字段)
searchForm: {
zymc: '', zyfx: '',
pxlxEnabled: false, pxlx: '',
pxccEnabled: false, pxcc: '',
zdyfl: '',
zgzyEnabled: false, zgzy: '',
zydm: '', xnz: '',
pxlx2Enabled: false, pxlx2: '',
qyztEnabled: false, qyzt: '启用',
zybb: '', pymb: ''
zymc: '',
zydm: '',
pxlx: '',
pxcc: ''
},
// 列表
loading: false,
tableData: [],
selectedRows: [],
total: 0,
pageNum: 1,
pageSize: 10,
pageSize: 20,
// 上传
selectedFile: null,
fileName: '',
// 新增/编辑弹窗
dialogVisible: false,
dialogTitle: '新建',
editLoading: false,
addLoading: false,
addForm: {
zydh: '', zymc: '', zyfx: '', zydm: '', zybb: '', pxcc: '', pxlx: '',
pxlx2: '', xylb: '', xnz: '', xqs: 1, zdyfl: '', jxgljgbh: '',
gfmc: '', zybz: '', pymb: ''
dialog: {
visible: false,
title: '',
isEdit: false,
submitting: false,
form: this.createEmptyForm()
},
// 详情弹窗
detail: {
visible: false,
loading: false,
data: {}
},
rules: {
zydh: [{ required: true, message: '请输入专业代号', trigger: 'blur' }],
zymc: [{ required: true, message: '请输入专业名称', trigger: 'blur' }],
zydm: [{ required: true, message: '请输入专业代码', trigger: 'blur' }],
pxcc: [{ required: true, message: '请输入培训层次', trigger: 'blur' }],
pxlx: [{ required: true, message: '请输入培训类型', trigger: 'blur' }],
pymb: [{ required: true, message: '请输入培养目标', trigger: 'blur' }]
pxlx: [{ required: true, message: '请输入培训类型', trigger: 'blur' }]
}
}
},
computed: {
pxlxOptions() { return this.options.pxlx },
pxccOptions() { return this.options.pxcc },
zgzyOptions() { return this.options.zgzy },
pxlx2Options() { return this.options.pxlx2 }
},
created() {
// TODO: 待后端接口,拉取下拉选项与列表
// this.loadOptions()
// this.getList()
this.fetchList()
},
methods: {
/** 查询 */
handleSearch() {
// TODO: 调用查询接口
},
/** 重置 */
handleReset() {
this.searchForm = {
zymc: '', zyfx: '', pxlxEnabled: false, pxlx: '',
pxccEnabled: false, pxcc: '', zdyfl: '', zgzyEnabled: false, zgzy: '',
zydm: '', xnz: '', pxlx2Enabled: false, pxlx2: '',
qyztEnabled: false, qyzt: '启用', zybb: '', pymb: ''
/* ---------- 列表加载 ---------- */
fetchList() {
this.loading = true
const params = {
pageNum: this.pageNum,
pageSize: this.pageSize
}
Object.keys(this.searchForm).forEach(key => {
const value = this.searchForm[key]
if (value !== '' && value !== null && value !== undefined) {
params[key] = value
}
})
listTraining(params).then(response => {
const data = response.data || {}
this.tableData = data.records || []
this.total = data.total || 0
}).catch(() => {
this.tableData = []
this.total = 0
}).finally(() => {
this.loading = false
})
},
/** 新建 */
handleOpenAdd() {
// TODO: 打开新建对话框
this.dialogVisible = true
this.dialogTitle = '新建人才培养方案'
/* ---------- 查询 / 重置 ---------- */
handleQuery() {
this.pageNum = 1
this.fetchList()
},
/** 下载 */
handleReset() {
this.searchForm = { zymc: '', zydm: '', pxlx: '', pxcc: '' }
this.pageNum = 1
this.fetchList()
},
/* ---------- 分页 ---------- */
handleSizeChange(size) {
this.pageSize = size
this.pageNum = 1
this.fetchList()
},
handlePageChange(page) {
this.pageNum = page
this.fetchList()
},
/* ---------- 新增 / 编辑 ---------- */
handleAdd() {
this.dialog.title = '新建人才培养方案'
this.dialog.isEdit = false
this.dialog.form = this.createEmptyForm()
this.dialog.visible = true
this.$nextTick(() => {
if (this.$refs.trainingForm) this.$refs.trainingForm.clearValidate()
})
},
handleEdit(row) {
this.dialog.title = '编辑人才培养方案'
this.dialog.isEdit = true
this.dialog.form = Object.assign({}, this.createEmptyForm(), row)
this.dialog.visible = true
this.$nextTick(() => {
if (this.$refs.trainingForm) this.$refs.trainingForm.clearValidate()
})
},
submitDialog() {
this.$refs.trainingForm.validate(valid => {
if (!valid) return
this.dialog.submitting = true
const payload = this.cleanPayload(this.dialog.form)
const isEdit = this.dialog.isEdit
const req = isEdit ? updateTraining(payload) : addTraining(payload)
req.then(() => {
this.$message.success(isEdit ? '修改成功' : '新增成功')
this.dialog.visible = false
this.fetchList()
}).catch(() => {}).finally(() => {
this.dialog.submitting = false
})
})
},
/* ---------- 详情 ---------- */
handleDetail(row) {
this.detail.visible = true
this.detail.loading = true
this.detail.data = {}
getTraining(row.zydh).then(response => {
this.detail.data = response.data || {}
}).catch(() => {
this.detail.data = {}
}).finally(() => {
this.detail.loading = false
})
},
/* ---------- 停用(逻辑删除) ---------- */
handleDisable(row) {
this.$confirm('确定停用人才培养方案「' + (row.zymc || row.zydh) + '」吗?停用后列表不再展示。', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
return disableTraining(row.zydh)
}).then(() => {
this.$message.success('停用成功')
this.fetchList()
}).catch(() => {})
},
/* ---------- 下载 / 模板下载 / 上传(后端未提供接口) ---------- */
handleDownload() {
// TODO: 列表数据下载
this.$message.warning('后端暂未提供该接口')
},
/** 模板下载 */
handleTemplateDownload() {
// TODO: 模板下载
this.$message.warning('后端暂未提供该接口')
},
/** 选择文件 */
handleChooseFile() {
this.$refs.fileInputRef && this.$refs.fileInputRef.click()
},
@@ -352,76 +448,78 @@ export default {
const file = e.target.files && e.target.files[0]
this.selectedFile = file || null
this.fileName = file ? file.name : ''
// TODO: 校验文件类型后保留
},
/** 上传数据 */
handleUpload() {
// TODO: 上传接口(上传 selectedFile)
this.$message.warning('后端暂未提供该接口')
},
/** 表格多选 */
handleSelectionChange(rows) {
this.selectedRows = rows
/* ---------- 工具 ---------- */
createEmptyForm() {
return {
zydh: '',
zymc: '',
zyfx: '',
zydm: '',
pxcc: '',
pxlx: '',
pxlx2: '',
xylb: '',
xnz: '',
xqs: null,
zybb: '',
zdyfl: '',
jxgljgbh: '',
gfmc: '',
zygf: '',
jc: '',
zgzy: false,
zybz: '',
pymb: ''
}
},
/** 编辑 */
handleEdit(row) {
// TODO: 回填并打开编辑对话框
this.dialogVisible = true
this.dialogTitle = '编辑人才培养方案'
},
/** 停用 */
handleDisable(row) {
// TODO: 停用接口
},
/** 分页 */
handlePageChange(val) {
this.pageNum = val
// TODO: 重新查询
},
handleSizeChange(val) {
this.pageSize = val
// TODO: 重新查询
},
/** 取消 */
handleCancel() {
this.dialogVisible = false
this.$refs.addFormRef && this.$refs.addFormRef.clearValidate()
},
/** 保存 */
handleSubmit() {
this.$refs.addFormRef.validate(valid => {
if (!valid) return
// TODO: 新增/编辑接口
this.dialogVisible = false
/** 移除空值(''/null/undefined),保留 0/false 等有效值 */
cleanPayload(obj) {
const payload = {}
Object.keys(obj).forEach(key => {
const value = obj[key]
if (value !== '' && value !== null && value !== undefined) {
payload[key] = value
}
})
return payload
},
isTrue(val) {
return val === true || val === 1 || val === '1' || val === 'true'
},
fmtYesNo(val) {
return this.isTrue(val) ? '是' : '否'
},
fmtDateTime(val) {
return (val || '').substring(0, 19) || '-'
},
fmtVal(val) {
return val === '' || val === null || val === undefined ? '-' : val
}
}
}
</script>
<style scoped lang="scss">
.app-container {
padding: 20px;
}
.training-plan-page {
.search-card {
margin-bottom: 16px;
}
.search-form {
.w-full {
width: 100%;
}
.search-form {
.w-full {
width: 100%;
}
.search-footer {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 12px;
margin-top: 8px;
padding-top: 12px;
border-top: 1px solid #ebeef5;
.notice {
font-size: 13px;
color: #909399;
.search-actions {
padding-top: 4px;
}
}
}
@@ -466,10 +564,35 @@ export default {
}
.table-card {
.pagination-wrapper {
margin-top: 14px;
.list-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
.list-title {
font-size: 16px;
font-weight: 600;
color: #303133;
}
}
.pagination {
margin-top: 16px;
text-align: right;
}
}
.detail-body {
min-height: 60px;
}
}
</style>
.danger-text-btn {
color: #f56c6c;
&:hover {
color: #f78989;
}
}
</style>
@@ -1,13 +1,444 @@
<template>
<placeholder-page title="教学场地" icon="build" description="维护教学楼、教室、实验室等教学场地资源,支持场地类型、容量与使用状态管理。" />
<div class="app-container venue-page">
<!-- 查询条件 -->
<el-card shadow="never" class="search-card">
<el-form :model="searchForm" label-width="100px" class="search-form" @submit.native.prevent>
<el-row :gutter="24">
<el-col :xs="24" :md="8">
<el-form-item label="教室编号">
<el-input v-model="searchForm.jsbh" placeholder="请输入教室编号" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :md="8">
<el-form-item label="教室名称">
<el-input v-model="searchForm.jsmc" placeholder="请输入教室名称" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :md="8">
<el-form-item label="教学楼代号">
<el-input v-model="searchForm.jxldh" placeholder="请输入教学楼代号" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :md="8">
<el-form-item label="教学场地类型">
<el-select v-model="searchForm.jxcdlx" placeholder="请选择场地类型" clearable filterable allow-create
default-first-option class="w-full">
<el-option v-for="item in jxcdlxOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :md="8">
<el-form-item label="停用">
<el-select v-model="searchForm.ty" placeholder="全部" clearable class="w-full">
<el-option :value="true" label="停用" />
<el-option :value="false" label="正常" />
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :md="8">
<div class="search-actions">
<el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
<el-button icon="el-icon-refresh" @click="handleReset">重置</el-button>
</div>
</el-col>
</el-row>
</el-form>
</el-card>
<!-- 数据列表 -->
<el-card shadow="never" class="table-card">
<div class="list-header">
<div class="list-title">教室列表</div>
<div class="list-actions">
<el-button type="primary" icon="el-icon-plus" @click="handleAdd">新增</el-button>
</div>
</div>
<el-table v-loading="loading" :data="tableData" border stripe highlight-current-row>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="jsbh" label="教室编号" min-width="120" show-overflow-tooltip header-align="center" />
<el-table-column prop="jsmc" label="教室名称" min-width="140" show-overflow-tooltip header-align="center" />
<el-table-column prop="jxldh" label="教学楼代号" min-width="110" show-overflow-tooltip header-align="center" />
<el-table-column prop="jxcdlx" label="教学场地类型" min-width="120" show-overflow-tooltip header-align="center" />
<el-table-column prop="jc" label="简称" min-width="90" show-overflow-tooltip header-align="center" />
<el-table-column prop="rnrs" label="容纳人数" width="90" align="center" />
<el-table-column prop="mj" label="面积" width="90" align="center" />
<el-table-column prop="ksrnrs" label="考试容纳人数" width="110" align="center" />
<el-table-column prop="ewyxkbs" label="额外可开班数" width="100" align="center" />
<el-table-column label="虚实类型" width="90" align="center">
<template slot-scope="scope">{{ fmtXslx(scope.row.xslx) }}</template>
</el-table-column>
<el-table-column label="停用" width="80" align="center">
<template slot-scope="scope">
<el-tag :type="isTrue(scope.row.ty) ? 'danger' : 'success'" size="mini">
{{ isTrue(scope.row.ty) ? '停用' : '正常' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="sssb" label="设施设备" min-width="140" show-overflow-tooltip header-align="center" />
<el-table-column label="操作" width="140" align="center" fixed="right">
<template slot-scope="scope">
<el-button type="text" size="mini" icon="el-icon-edit" @click="handleEdit(scope.row)">编辑</el-button>
<el-button type="text" size="mini" icon="el-icon-delete" class="danger-text-btn" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
class="pagination"
background
layout="total, sizes, prev, pager, next"
:current-page="pageNum"
:page-size="pageSize"
:total="total"
:page-sizes="[10, 20, 50, 100]"
@size-change="handleSizeChange"
@current-change="handlePageChange"
/>
</el-card>
<!-- 新增/编辑弹窗 -->
<el-dialog :title="dialog.title" :visible.sync="dialog.visible" width="780px" append-to-body
:close-on-click-modal="false">
<el-form ref="classroomForm" :model="dialog.form" :rules="rules" label-width="110px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="主键编号" prop="id">
<el-input v-model="dialog.form.id" placeholder="请输入主键编号" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="教室编号" prop="jsbh">
<el-input v-model="dialog.form.jsbh" placeholder="请输入教室编号" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="教室名称" prop="jsmc">
<el-input v-model="dialog.form.jsmc" placeholder="请输入教室名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="教学楼代号">
<el-input v-model="dialog.form.jxldh" placeholder="请输入教学楼代号" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="教学场地类型">
<el-select v-model="dialog.form.jxcdlx" placeholder="请选择场地类型" clearable filterable allow-create
default-first-option class="w-full">
<el-option v-for="item in jxcdlxOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="简称">
<el-input v-model="dialog.form.jc" placeholder="请输入简称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="容纳人数">
<el-input-number v-model="dialog.form.rnrs" :min="0" controls-position="right" class="w-full" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="面积">
<el-input-number v-model="dialog.form.mj" :min="0" :precision="1" controls-position="right" class="w-full" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="考试容纳人数">
<el-input-number v-model="dialog.form.ksrnrs" :min="0" controls-position="right" class="w-full" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="额外可开班数">
<el-input-number v-model="dialog.form.ewyxkbs" :min="0" controls-position="right" class="w-full" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="序号">
<el-input v-model="dialog.form.xh" placeholder="请输入序号" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="拼音">
<el-input v-model="dialog.form.py" placeholder="请输入拼音" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="虚实类型">
<el-radio-group v-model="dialog.form.xslx">
<el-radio :label="true">实教</el-radio>
<el-radio :label="false">非实教</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="停用">
<el-radio-group v-model="dialog.form.ty">
<el-radio :label="false">正常</el-radio>
<el-radio :label="true">停用</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="设施设备">
<el-input v-model="dialog.form.sssb" placeholder="请输入设施设备" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="备注">
<el-input v-model="dialog.form.bz" type="textarea" :rows="2" placeholder="请输入备注" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialog.visible = false">取 消</el-button>
<el-button type="primary" :loading="dialog.submitting" @click="submitDialog">确 定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import PlaceholderPage from '@/components/PlaceholderPage'
import {
addClassroom,
deleteClassroom,
updateClassroom,
listClassroom
} from '@/api/teachBusiness/classroom'
export default {
name: 'Venue',
components: { PlaceholderPage }
data() {
return {
loading: false,
searchForm: {
jsbh: '',
jsmc: '',
jxldh: '',
jxcdlx: '',
ty: ''
},
tableData: [],
total: 0,
pageNum: 1,
pageSize: 20,
dialog: {
visible: false,
title: '',
submitting: false,
form: this.createEmptyForm()
},
rules: {
id: [{ required: true, message: '请输入主键编号', trigger: 'blur' }],
jsbh: [{ required: true, message: '请输入教室编号', trigger: 'blur' }],
jsmc: [{ required: true, message: '请输入教室名称', trigger: 'blur' }]
}
}
},
computed: {
/** 教学场地类型下拉:由已加载数据中的去重值动态生成,避免硬编码 */
jxcdlxOptions() {
const set = new Set()
this.tableData.forEach(row => {
if (row.jxcdlx) set.add(row.jxcdlx)
})
return Array.from(set)
}
},
created() {
this.fetchList()
},
methods: {
/* ---------- 列表加载 ---------- */
fetchList() {
this.loading = true
const params = {
pageNum: this.pageNum,
pageSize: this.pageSize
}
Object.keys(this.searchForm).forEach(key => {
const value = this.searchForm[key]
if (value !== '' && value !== null && value !== undefined) {
params[key] = value
}
})
listClassroom(params).then(response => {
const data = response.data || {}
this.tableData = data.records || []
this.total = data.total || 0
}).catch(() => {
this.tableData = []
this.total = 0
}).finally(() => {
this.loading = false
})
},
/* ---------- 查询 / 重置 ---------- */
handleQuery() {
this.pageNum = 1
this.fetchList()
},
handleReset() {
this.searchForm = {
jsbh: '',
jsmc: '',
jxldh: '',
jxcdlx: '',
ty: ''
}
this.pageNum = 1
this.fetchList()
},
/* ---------- 分页 ---------- */
handleSizeChange(size) {
this.pageSize = size
this.pageNum = 1
this.fetchList()
},
handlePageChange(page) {
this.pageNum = page
this.fetchList()
},
/* ---------- 新增 / 编辑 ---------- */
handleAdd() {
this.dialog.title = '新增教室'
this.dialog.form = this.createEmptyForm()
this.dialog.visible = true
this.$nextTick(() => {
if (this.$refs.classroomForm) this.$refs.classroomForm.clearValidate()
})
},
handleEdit(row) {
this.dialog.title = '编辑教室'
this.dialog.form = Object.assign({}, this.createEmptyForm(), row)
this.dialog.visible = true
this.$nextTick(() => {
if (this.$refs.classroomForm) this.$refs.classroomForm.clearValidate()
})
},
submitDialog() {
this.$refs.classroomForm.validate(valid => {
if (!valid) return
this.dialog.submitting = true
const payload = this.cleanPayload(this.dialog.form)
const isEdit = !!payload.id
const req = isEdit ? updateClassroom(payload) : addClassroom(payload)
req.then(() => {
this.$message.success(isEdit ? '修改成功' : '新增成功')
this.dialog.visible = false
this.fetchList()
}).catch(() => {}).finally(() => {
this.dialog.submitting = false
})
})
},
/* ---------- 删除(后端仅提供单条删除,参数为 id) ---------- */
handleDelete(row) {
this.$confirm('确定删除教室「' + (row.jsmc || row.jsbh) + '」吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
return deleteClassroom(row.id)
}).then(() => {
this.$message.success('删除成功')
this.fetchList()
}).catch(() => {})
},
/* ---------- 工具 ---------- */
createEmptyForm() {
return {
id: '',
jsbh: '',
jsmc: '',
jxldh: '',
jxcdlx: '',
jc: '',
rnrs: null,
mj: null,
ksrnrs: null,
ewyxkbs: null,
xh: '',
py: '',
xslx: false,
ty: false,
sssb: '',
bz: ''
}
},
/** 移除空值(''/null/undefined),保留 0/false 等有效值 */
cleanPayload(obj) {
const payload = {}
Object.keys(obj).forEach(key => {
const value = obj[key]
if (value !== '' && value !== null && value !== undefined) {
payload[key] = value
}
})
return payload
},
isTrue(val) {
return val === true || val === 1 || val === '1' || val === 'true'
},
fmtXslx(val) {
return this.isTrue(val) ? '实教' : '非实教'
}
}
}
</script>
<style scoped lang="scss">
.app-container {
padding: 20px;
}
.venue-page {
.search-card {
margin-bottom: 16px;
.search-form {
.w-full {
width: 100%;
}
.search-actions {
padding-top: 4px;
}
}
}
.table-card {
.list-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
.list-title {
font-size: 16px;
font-weight: 600;
color: #303133;
}
}
.pagination {
margin-top: 16px;
text-align: right;
}
}
}
.danger-text-btn {
color: #f56c6c;
&:hover {
color: #f78989;
}
}
</style>
@@ -44,7 +44,6 @@
</el-row>
</el-form>
<div class="notice">注意:勾选"选择框"表示启用该项对应的查询条件;文本输入框非空白表示启用对应的查询条件。</div>
<div class="query-footer">
<el-button class="gray-btn" @click="handleQuery">查询</el-button>
</div>
@@ -99,15 +98,7 @@
<el-table-column prop="textbook" label="教材" min-width="120" show-overflow-tooltip />
<el-table-column prop="teacher" label="实施教员" width="90" align="center" />
<el-table-column prop="place" label="教学场地" width="100" show-overflow-tooltip />
<el-table-column label="显示可选队别班次" min-width="150" show-overflow-tooltip>
<template slot="header">
<div class="checkable-header">
<el-checkbox />
<span>显示可选队别班次</span>
</div>
</template>
<template slot-scope="{ row }">{{ row.teamClass }}</template>
</el-table-column>
<el-table-column prop="teamClass" label="显示可选队别班次" min-width="150" align="center" show-overflow-tooltip />
<el-table-column label="计划学时/运行学时/学分" width="130" align="center">
<template slot="header">
<div class="triple-header">
@@ -488,13 +479,6 @@ export default {
}
}
.checkable-header {
display: flex;
align-items: center;
gap: 4px;
white-space: nowrap;
}
.triple-header,
.triple-cell {
display: flex;
File diff suppressed because it is too large Load Diff
@@ -1,82 +1,79 @@
<template>
<el-dialog
:visible="dialogVisible"
title="教学班次学期信息明细"
width="720px"
:title="isEdit ? '编辑班次学期信息' : '教学班次学期信息明细'"
width="620px"
class="class-detail-dialog"
:close-on-click-modal="false"
@update:visible="val => dialogVisible = val"
>
<el-form label-width="100px" class="detail-form">
<el-row :gutter="24">
<!-- 左列 -->
<el-form ref="formRef" :model="form" :rules="rules" label-width="110px" class="detail-form">
<!-- 班次信息(只读展示) -->
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="班次">
<el-input v-model="form.bc" readonly />
</el-form-item>
<el-form-item label="专业">
<el-input v-model="form.xkzy" readonly />
</el-form-item>
<el-form-item label="年级">
<el-input v-model="form.nj" readonly />
</el-form-item>
<el-form-item label="学期">
<div class="semester-field">
<el-input v-model="form.xq" readonly />
<el-input v-model="form.ghn" class="plan-input" placeholder="" />
</div>
</el-form-item>
<el-form-item label="开学日期">
<el-date-picker v-model="form.kxrq" type="date" format="yyyy年M月d日" value-format="yyyy-MM-dd" style="width: 100%" />
</el-form-item>
<el-form-item label="结束日期">
<el-date-picker v-model="form.jsrq" type="date" format="yyyy年M月d日" value-format="yyyy-MM-dd" style="width: 100%" />
</el-form-item>
<el-form-item label="学期周数">
<el-input v-model="form.zs" />
<el-input :value="form.xydmc || form.xydbh" readonly />
</el-form-item>
</el-col>
<!-- 右列 -->
<el-col :span="12">
<el-form-item label="教学任务">
<el-select v-model="form.jxrw" clearable placeholder="请选择" style="width: 100%">
<el-option v-for="opt in taskOptions" :key="opt" :label="opt" :value="opt" />
<el-form-item label="班次编号">
<el-input v-model="form.xydbh" :readonly="isEdit" :disabled="isEdit" placeholder="编辑模式不可修改" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="年度" prop="nd">
<el-select v-model="form.nd" placeholder="请选择年度" style="width: 100%">
<el-option v-for="y in yearOptions" :key="y" :label="`${y}年`" :value="y" />
</el-select>
</el-form-item>
<el-form-item label="学期层次">
<el-select v-model="form.xqcc" style="width: 100%">
<el-option v-for="opt in levelOptions" :key="opt" :label="opt" :value="opt" />
</el-select>
</el-form-item>
<el-form-item label="开放教员排课">
<el-checkbox v-model="form.kfpy" />
</el-form-item>
<el-form-item label="查找教室">
<el-select v-model="form.zxjs" style="width: 100%">
<el-option v-for="opt in roomOptions" :key="opt" :label="opt" :value="opt" />
</el-select>
</el-form-item>
<el-form-item label="教学楼">
<el-select v-model="form.jxl" style="width: 100%">
<el-option v-for="opt in buildingOptions" :key="opt" :label="opt" :value="opt" />
</el-select>
</el-form-item>
<el-form-item label="专用教室">
<el-select v-model="form.zyjs" style="width: 100%">
<el-option v-for="opt in zyjsOptions" :key="opt" :label="opt" :value="opt" />
</el-col>
<el-col :span="12">
<el-form-item label="学期第次" prop="xqdc">
<el-select v-model="form.xqdc" placeholder="请选择学期" style="width: 100%">
<el-option v-for="opt in levelOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<!-- 备注 -->
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="开学日期" prop="kxrq">
<el-date-picker v-model="form.kxrq" type="date" value-format="yyyy-MM-dd" placeholder="请选择" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="结束日期" prop="jsrq">
<el-date-picker v-model="form.jsrq" type="date" value-format="yyyy-MM-dd" placeholder="请选择" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="专用教室">
<el-input v-model="form.zyjsbh" placeholder="请输入专用教室编号" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="教学任务">
<el-select v-model="form.jxrwbh" clearable filterable placeholder="请选择教学任务" style="width: 100%">
<el-option v-for="opt in taskOptions" :key="opt.bh" :label="opt.rwmc" :value="opt.bh" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="开放教员排课">
<el-checkbox v-model="form.kfjypk">开放教员排课</el-checkbox>
</el-form-item>
<el-form-item label="备注">
<el-input v-model="form.bz" type="textarea" :rows="3" placeholder="" style="width: 100%" />
<el-input v-model="form.bz" type="textarea" :rows="3" placeholder="请输入备注" clearable />
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleConfirm">确定</el-button>
<el-button type="primary" :loading="saving" @click="handleSubmit">确定</el-button>
</div>
</el-dialog>
</template>
@@ -86,35 +83,32 @@ export default {
name: 'ClassSemesterDetailDialog',
props: {
visible: { type: Boolean, default: false },
/** 选中的班次信息 */
classInfo: { type: Object, default: null },
/** 学期名称 */
semesterName: { type: String, default: '2017年秋季学期' }
/** 编辑模式:edit / add */
mode: { type: String, default: 'add' },
/** 编辑时的班次学期记录 */
semester: { type: Object, default: null },
/** 新增时选中的班次(学员队)信息 */
teamInfo: { type: Object, default: null },
/** 年度下拉 */
yearOptions: { type: Array, default: () => [] },
/** 教学任务下拉({ bh, rwmc }) */
taskOptions: { type: Array, default: () => [] }
},
data() {
return {
form: {
bc: '班次0633', // 班次
xkzy: '专业209', // 专业
nj: '2017级', // 年级
xq: this.semesterName, // 学期
ghn: '规划内', // 规划内
kxrq: '2017-09-03', // 开学日期
jsrq: '2018-02-11', // 结束日期
zs: 24, // 学期周数
jxrw: '', // 教学任务
xqcc: '第1学期', // 学期层次
kfpy: true, // 开放教员排课
zxjs: '10-03&10-03(50)', // 查找教室
jxl: '5号楼', // 教学楼
zyjs: '作战实验中心学术报告厅', // 专用教室
bz: '' // 备注
},
taskOptions: ['2017年下半年教学任务', '研究生2017下半年教学任务'],
levelOptions: ['第1学期', '第2学期', '第3学期'],
roomOptions: ['10-03&10-03(50)', '10-04&10-04(40)', '9-01'],
buildingOptions: ['5号楼', '6号楼', '综合楼'],
zyjsOptions: ['作战实验中心学术报告厅', '综合教学楼101', '学术报告厅']
form: this.createEmptyForm(),
levelOptions: [
{ label: '第1学期', value: 1 },
{ label: '第2学期', value: 2 },
{ label: '第3学期', value: 3 }
],
saving: false,
rules: {
nd: [{ required: true, message: '请选择年度', trigger: 'change' }],
xqdc: [{ required: true, message: '请选择学期第次', trigger: 'change' }],
kxrq: [{ required: true, message: '请选择开学日期', trigger: 'change' }],
jsrq: [{ required: true, message: '请选择结束日期', trigger: 'change' }]
}
}
},
computed: {
@@ -125,22 +119,93 @@ export default {
set(val) {
this.$emit('update:visible', val)
}
},
isEdit() {
return this.mode === 'edit'
}
},
watch: {
// 每次打开时按传入的班次信息初始化表单
visible(val) {
if (!val) return
this.form.bc = (this.classInfo && this.classInfo.bc) || '班次0633'
this.form.xkzy = (this.classInfo && this.classInfo.xkzy) || '专业209'
this.form.nj = (this.classInfo && this.classInfo.nj) || '2017级'
this.form.xq = this.semesterName
this.initForm()
this.$nextTick(() => {
if (this.$refs.formRef) this.$refs.formRef.clearValidate()
})
}
},
methods: {
handleConfirm() {
this.$emit('confirm')
this.dialogVisible = false
createEmptyForm() {
return {
bh: '',
xydbh: '',
xydmc: '',
nd: undefined,
xqdc: undefined,
kxrq: '',
jsrq: '',
zyjsbh: '',
jxrwbh: undefined,
kfjypk: false,
bz: ''
}
},
/** 打开时按编辑/新增模式初始化表单 */
initForm() {
const form = this.createEmptyForm()
if (this.isEdit && this.semester) {
const row = this.semester
form.bh = row.bh || ''
form.xydbh = row.xydbh || ''
form.xydmc = row.xydmc || ''
form.nd = row.nd
form.xqdc = row.xqdc
form.kxrq = row.kxrq ? String(row.kxrq).slice(0, 10) : ''
form.jsrq = row.jsrq ? String(row.jsrq).slice(0, 10) : ''
form.zyjsbh = row.zyjsbh || ''
form.jxrwbh = row.jxrwbh || undefined
form.kfjypk = Number(row.kfjypk) === 1
form.bz = row.bz || ''
} else if (this.teamInfo) {
form.xydbh = this.teamInfo.xydbh || ''
form.xydmc = this.teamInfo.xydmc || ''
// 默认取当前选中年度的第一学期,日期可后续修改
form.nd = this.yearOptions[0]
form.xqdc = 1
}
this.form = form
},
/** 移除空值(保留 0 等有效值) */
cleanPayload(obj) {
const payload = {}
Object.keys(obj).forEach(key => {
const value = obj[key]
if (value !== '' && value !== null && value !== undefined) {
payload[key] = value
}
})
return payload
},
handleSubmit() {
this.$refs.formRef.validate(valid => {
if (!valid) return
if (!this.form.xydbh) {
this.$message.warning('缺少班次编号,无法保存')
return
}
const payload = this.cleanPayload({
bh: this.form.bh,
xydbh: this.form.xydbh,
nd: this.form.nd,
xqdc: this.form.xqdc,
kxrq: this.form.kxrq,
jsrq: this.form.jsrq,
zyjsbh: this.form.zyjsbh,
jxrwbh: this.form.jxrwbh,
kfjypk: this.form.kfjypk ? 1 : 0,
bz: this.form.bz
})
this.$emit('submit', payload)
})
}
}
}
@@ -153,22 +218,6 @@ export default {
::v-deep .el-input__inner[readonly] {
background-color: #f5f7fa;
}
.semester-field {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
.el-input:first-child {
flex: 1;
}
.plan-input {
flex-shrink: 0;
width: 120px;
}
}
}
}
</style>
@@ -1,148 +1,71 @@
<template>
<el-dialog
:visible="dialogVisible"
title="学员队选取"
width="90%"
top="5vh"
:title="title"
width="900px"
top="8vh"
:close-on-click-modal="false"
class="team-select-dialog"
@update:visible="val => dialogVisible = val"
>
<div class="team-select">
<!-- ==================== 1. 查询条件区域 ==================== -->
<!-- ==================== 操作说明 ==================== -->
<div v-if="excludeNote" class="note-bar">
<i class="el-icon-info"></i>
<span>{{ excludeNote }}</span>
</div>
<!-- ==================== 查询条件区域 ==================== -->
<div class="query-panel">
<el-form label-width="88px" class="query-form">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="专业">
<div class="check-field">
<el-checkbox v-model="queryForm.zyEnabled" />
<el-select v-model="queryForm.zy" :disabled="!queryForm.zyEnabled" style="flex: 1">
<el-option v-for="opt in zyOptions" :key="opt" :label="opt" :value="opt" />
</el-select>
</div>
</el-form-item>
<el-form-item label="年级">
<div class="check-field">
<el-checkbox v-model="queryForm.njEnabled" />
<el-input v-model="queryForm.nj" :disabled="!queryForm.njEnabled" placeholder="" />
</div>
</el-form-item>
<el-form-item label="在校状态">
<div class="check-field">
<el-checkbox v-model="queryForm.zxztEnabled" />
<el-select v-model="queryForm.zxzt" :disabled="!queryForm.zxztEnabled" style="flex: 1">
<el-option v-for="opt in zxztOptions" :key="opt" :label="opt" :value="opt" />
</el-select>
</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="学员队名称">
<div class="check-field">
<el-checkbox v-model="queryForm.xydmcEnabled" />
<el-input v-model="queryForm.xydmc" :disabled="!queryForm.xydmcEnabled" placeholder="" />
</div>
</el-form-item>
<el-form-item label="任务类别">
<div class="check-field">
<el-checkbox v-model="queryForm.rwlbEnabled" />
<el-select v-model="queryForm.rwlb" :disabled="!queryForm.rwlbEnabled" style="flex: 1">
<el-option v-for="opt in rwlbOptions" :key="opt" :label="opt" :value="opt" />
</el-select>
</div>
</el-form-item>
</el-col>
</el-row>
<el-form :inline="true" class="query-form" @submit.native.prevent>
<el-form-item label="班次编号">
<el-input
v-model="queryForm.xydbh"
placeholder="请输入班次编号(模糊)"
clearable
style="width: 220px"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" size="small" icon="el-icon-search" @click="handleQuery">查询</el-button>
<el-button size="small" @click="handleResetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 分页控制区域 -->
<div class="pager-row">
<button type="button" class="gray-btn" @click="handleQuery">查询</button>
<button type="button" class="gray-btn" @click="handleFirst">首页</button>
<button type="button" class="gray-btn" @click="handlePrev">上页</button>
<button type="button" class="gray-btn" @click="handleNext">下页</button>
<button type="button" class="gray-btn" @click="handleLast">末页</button>
<el-input-number v-model="currentPage" :min="1" size="small" controls-position="right" style="width: 90px" />
<span class="pager-label">每页条数</span>
<el-select v-model="pageSize" size="small" style="width: 70px">
<el-option v-for="n in pageSizeOptions" :key="n" :label="String(n)" :value="n" />
</el-select>
<button type="button" class="gray-btn" @click="handleLocate">定位</button>
<el-select v-model="displaySize" size="small" style="width: 80px">
<el-option v-for="n in displaySizeOptions" :key="n" :label="String(n)" :value="n" />
</el-select>
<button type="button" class="blue-btn" @click="handleSetPageSize">设置页项数</button>
</div>
</div>
<!-- ==================== 2. 上方数据表格 ==================== -->
<!-- ==================== 目标班次学期表格 ==================== -->
<div class="table-panel">
<el-table
:data="teamList"
border
stripe
highlight-current-row
size="small"
class="team-table"
@selection-change="handleTopSelectionChange"
>
<el-table-column type="selection" width="40" align="center" />
<el-table-column prop="xzdbm" label="行政队别名称" width="100" align="center" show-overflow-tooltip />
<el-table-column prop="xydmc" label="学员队名称" width="90" align="center" show-overflow-tooltip />
<el-table-column prop="nj" label="年级" width="70" align="center" show-overflow-tooltip />
<el-table-column prop="zy" label="专业" width="80" align="center" show-overflow-tooltip />
<el-table-column label="人数" width="60" align="center">
<template slot-scope="{ row }">{{ row.rs }}人</template>
</el-table-column>
<el-table-column prop="zxzt" label="在校状态" width="80" align="center" show-overflow-tooltip />
<el-table-column prop="rwlb" label="任务类别" width="80" align="center" show-overflow-tooltip />
<el-table-column prop="zyjs" label="专用教室" width="120" align="center" show-overflow-tooltip />
<el-table-column prop="rxrq" label="入学日期" width="120" align="center" show-overflow-tooltip />
<el-table-column prop="byrq" label="毕业日期" width="120" align="center" show-overflow-tooltip />
</el-table>
</div>
<!-- ==================== 3. 中间操作按钮区域 ==================== -->
<div class="mid-actions">
<span class="mid-label">已选择列表项</span>
<button type="button" class="gray-btn" @click="handleAdd">加入</button>
<button type="button" class="gray-btn" @click="handleRemove">移除</button>
<button type="button" class="gray-btn" @click="handleClear">清除</button>
<button type="button" class="gray-btn" @click="handleReset">重置</button>
</div>
<!-- ==================== 4. 下方数据表格(已选学员队列表) ==================== -->
<div class="table-panel">
<el-table
:data="selectedList"
ref="tableRef"
v-loading="loading"
:data="filteredList"
border
stripe
size="small"
max-height="360"
class="team-table"
@selection-change="handleBottomSelectionChange"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="40" align="center" />
<el-table-column prop="zy" label="专业" width="80" align="center" show-overflow-tooltip />
<el-table-column prop="xydmc" label="学员队名称" width="90" align="center" show-overflow-tooltip />
<el-table-column prop="nj" label="年级" width="70" align="center" show-overflow-tooltip />
<el-table-column label="人数" width="60" align="center">
<template slot-scope="{ row }">{{ row.rs }}人</template>
<el-table-column type="selection" width="44" align="center" reserve-selection />
<el-table-column type="index" label="序号" width="55" align="center" />
<el-table-column prop="nd" label="年度" width="80" align="center" show-overflow-tooltip />
<el-table-column label="学期第次" width="90" align="center">
<template slot-scope="{ row }">{{ xqdcLabel(row.xqdc) }}</template>
</el-table-column>
<el-table-column prop="rwlb" label="任务类别" width="80" align="center" show-overflow-tooltip />
<el-table-column prop="zxzt" label="在校状态" width="80" align="center" show-overflow-tooltip />
<el-table-column prop="zyjs" label="专用教室" width="120" align="center" show-overflow-tooltip />
<el-table-column prop="rxrq" label="入学日期" width="120" align="center" show-overflow-tooltip />
<el-table-column prop="byrq" label="毕业日期" width="120" align="center" show-overflow-tooltip />
<el-table-column prop="bz" label="备注" width="80" align="center" show-overflow-tooltip />
<el-table-column prop="xydbh" label="班次编号" min-width="150" align="left" header-align="center" show-overflow-tooltip />
<el-table-column prop="kxrq" label="开学日期" width="110" align="center" :formatter="fmtDate" />
<el-table-column prop="jsrq" label="结束日期" width="110" align="center" :formatter="fmtDate" />
<el-table-column prop="bz" label="备注" min-width="120" align="left" header-align="center" show-overflow-tooltip />
</el-table>
<el-empty v-if="!selectedList.length" description="暂无已选学员队,请从上方表格加入" :image-size="60" />
<el-empty v-if="!filteredList.length" description="暂无可选的目标班次学期" :image-size="60" />
</div>
<!-- ==================== 5. 底部按钮区域 ==================== -->
<!-- ==================== 底部按钮区域 ==================== -->
<div class="footer-actions">
<button type="button" class="gray-btn" @click="handleConfirm">确定</button>
<button type="button" class="gray-btn" @click="handleCancel">取消</button>
<span class="selected-count">已选择 {{ selection.length }} 个班次学期</span>
<el-button size="small" @click="dialogVisible = false">取消</el-button>
<el-button size="small" type="primary" :disabled="!selection.length" @click="handleConfirm">确定</el-button>
</div>
</div>
</el-dialog>
@@ -152,52 +75,21 @@
export default {
name: 'ClassTeamSelectDialog',
props: {
visible: { type: Boolean, default: false }
visible: { type: Boolean, default: false },
/** 标题(可选) */
title: { type: String, default: '目标班次学期选取' },
/** 可选的目标班次学期列表(含 bh/nd/xqdc/xydbh/kxrq/jsrq 等) */
semesterList: { type: Array, default: () => [] },
/** 需要排除的班次学期编号(当前班次学期,不可复制给自己) */
excludeBh: { type: String, default: '' },
/** 顶部操作说明 */
excludeNote: { type: String, default: '' }
},
data() {
return {
// ==================== 查询条件区域 ====================
queryForm: {
zyEnabled: false, // 专业启用复选框(未勾选)
zy: '专业219', // 专业(默认值)
njEnabled: true, // 年级启用复选框(已勾选)
nj: '', // 年级(空值)
zxztEnabled: false, // 在校状态启用复选框(未勾选)
zxzt: '毕业', // 在校状态(默认值)
xydmcEnabled: true, // 学员队名称启用复选框(已勾选)
xydmc: '', // 学员队名称(空值)
rwlbEnabled: false, // 任务类别启用复选框(未勾选)
rwlb: '规划内' // 任务类别(默认值)
},
zyOptions: ['专业219', '专业220', '专业221'],
zxztOptions: ['毕业', '在校'],
rwlbOptions: ['规划内', '规划外'],
// ==================== 分页控制区域 ====================
currentPage: 1,
pageSize: 1,
displaySize: 100,
pageSizeOptions: [1, 5, 10, 20],
displaySizeOptions: [50, 100, 200],
// ==================== 上方数据表格(可选学员队列表) ====================
teamList: [
{ xzdbm: '教学五系', xydmc: '班次0417', nj: '2016级', zy: '专业113', rs: 36, zxzt: '毕业', rwlb: '规划内', zyjs: '8-011', rxrq: '2016年8月26日', byrq: '2018年1月21日' },
{ xzdbm: '教学七系', xydmc: '班次0423', nj: '2016级', zy: '专业114', rs: 9, zxzt: '毕业', rwlb: '规划内', zyjs: '8-212(南楼)', rxrq: '2016年8月26日', byrq: '2018年1月21日' },
{ xzdbm: '教学七系', xydmc: '班次0411', nj: '2016级', zy: '专业112', rs: 38, zxzt: '毕业', rwlb: '规划内', zyjs: '6-203', rxrq: '2016年8月26日', byrq: '2018年1月21日' },
{ xzdbm: '教学七系', xydmc: '班次0405', nj: '2016级', zy: '专业111', rs: 34, zxzt: '毕业', rwlb: '规划内', zyjs: '8-007', rxrq: '2016年8月26日', byrq: '2018年1月21日' },
{ xzdbm: '教学五系', xydmc: '班次0429', nj: '2017级', zy: '专业117', rs: 43, zxzt: '毕业', rwlb: '规划内', zyjs: '6-202', rxrq: '2017年8月24日', byrq: '2019年1月21日' },
{ xzdbm: '教学五系', xydmc: '班次0431', nj: '2017级', zy: '专业118', rs: 22, zxzt: '毕业', rwlb: '规划内', zyjs: '2-403', rxrq: '2017年8月24日', byrq: '2019年1月21日' },
{ xzdbm: '教学七系', xydmc: '班次0425', nj: '2017级', zy: '专业115', rs: 19, zxzt: '毕业', rwlb: '规划内', zyjs: '2-406', rxrq: '2017年8月24日', byrq: '2019年1月21日' },
{ xzdbm: '教学七系', xydmc: '班次0432', nj: '2017级', zy: '专业119', rs: 15, zxzt: '毕业', rwlb: '规划内', zyjs: '8-009(南楼)', rxrq: '2017年8月24日', byrq: '2019年1月21日' },
{ xzdbm: '教学七系', xydmc: '班次0434', nj: '2017级', zy: '专业120', rs: 45, zxzt: '毕业', rwlb: '规划内', zyjs: '6-303', rxrq: '2017年8月24日', byrq: '2019年1月21日' },
{ xzdbm: '教学六系', xydmc: '班次0427', nj: '2017级', zy: '专业116', rs: 64, zxzt: '毕业', rwlb: '规划内', zyjs: '2-501', rxrq: '2017年8月24日', byrq: '2019年1月21日' }
],
/** 上方表格多选 */
topSelection: [],
/** 下方数据表格(已选学员队列表) */
selectedList: [],
bottomSelection: []
loading: false,
queryForm: { xydbh: '' },
selection: []
}
},
computed: {
@@ -208,105 +100,54 @@ export default {
set(val) {
this.$emit('update:visible', val)
}
},
/** 排除当前班次学期后的可选列表 */
availableList() {
const excludeBh = this.excludeBh || ''
return (this.semesterList || []).filter(item => item.bh && String(item.bh) !== String(excludeBh))
},
/** 本地过滤后的列表 */
filteredList() {
const kw = (this.queryForm.xydbh || '').trim()
if (!kw) return this.availableList
return this.availableList.filter(item => item.xydbh && String(item.xydbh).includes(kw))
}
},
watch: {
visible(val) {
if (val) {
this.selection = []
this.queryForm.xydbh = ''
this.$nextTick(() => {
if (this.$refs.tableRef) this.$refs.tableRef.clearSelection()
})
}
}
},
methods: {
handleTopSelectionChange(rows) {
this.topSelection = rows
fmtDate(val) {
if (!val) return '-'
return String(val).slice(0, 10)
},
handleBottomSelectionChange(rows) {
this.bottomSelection = rows
xqdcLabel(val) {
if (val === 1 || val === '1') return '第1学期'
if (val === 2 || val === '2') return '第2学期'
if (val === 3 || val === '3') return '第3学期'
return val !== undefined && val !== null && val !== '' ? `第${val}学期` : '-'
},
// ==================== 分页控制 ====================
handleQuery() {
this.$message.success('查询(前端演示)')
handleSelectionChange(rows) {
this.selection = rows
},
handleFirst() {
this.currentPage = 1
this.$message.success('已跳转首页')
handleQuery() {},
handleResetQuery() {
this.queryForm.xydbh = ''
},
handlePrev() {
if (this.currentPage > 1) this.currentPage--
},
handleNext() {
this.currentPage++
},
handleLast() {
this.currentPage = this.teamList.length || 1
},
handleLocate() {
this.$message.success(`已定位到第 ${this.currentPage} 页`)
},
handleSetPageSize() {
this.$message.success(`每页显示条数已设置为 ${this.displaySize}`)
},
// ==================== 中间操作按钮 ====================
/** 加入:将上方表格选中行加入已选列表(去重) */
handleAdd() {
if (!this.topSelection.length) {
this.$message.warning('请先在上方表格选择学员队')
return
}
const existing = new Set(this.selectedList.map((r) => r.xydmc))
let added = 0
this.topSelection.forEach((r) => {
if (!existing.has(r.xydmc)) {
this.selectedList.push({ ...r, bz: '' })
added++
}
})
if (added) this.$message.success(`已加入 ${added} 个学员队`)
else this.$message.info('所选学员队已在列表中')
},
/** 移除:将下方表格选中行移除 */
handleRemove() {
if (!this.bottomSelection.length) {
this.$message.warning('请先在下方表格选择要移除的学员队')
return
}
const removeKeys = new Set(this.bottomSelection.map((r) => r.xydmc))
this.selectedList = this.selectedList.filter((r) => !removeKeys.has(r.xydmc))
this.$message.success(`已移除 ${this.bottomSelection.length} 个学员队`)
},
/** 清除:清空已选列表 */
handleClear() {
if (!this.selectedList.length) {
this.$message.info('已选列表为空')
return
}
this.selectedList = []
this.$message.success('已清空已选列表')
},
/** 重置:清空已选列表并重置查询条件 */
handleReset() {
this.selectedList = []
Object.assign(this.queryForm, {
zyEnabled: false, zy: '专业219',
njEnabled: true, nj: '',
zxztEnabled: false, zxzt: '毕业',
xydmcEnabled: true, xydmc: '',
rwlbEnabled: false, rwlb: '规划内'
})
this.currentPage = 1
this.$message.success('已重置')
},
// ==================== 底部按钮 ====================
handleConfirm() {
if (!this.selectedList.length) {
this.$message.warning('请先选择要应用的学员队')
if (!this.selection.length) {
this.$message.warning('请先勾选目标班次学期')
return
}
this.$emit('confirm', this.selectedList.slice())
this.dialogVisible = false
this.$message.success(`选定区域设置已应用于 ${this.selectedList.length} 个班次`)
},
handleCancel() {
this.$emit('confirm', this.selection.slice())
this.dialogVisible = false
}
}
@@ -319,40 +160,34 @@ export default {
flex-direction: column;
gap: 12px;
// ========== 1. 查询条件区域 ==========
// ========== 操作说明 ==========
.note-bar {
display: flex;
align-items: center;
gap: 6px;
padding: 8px 12px;
font-size: 13px;
color: #1e6fff;
background: #ecf5ff;
border: 1px solid #d9ecff;
border-radius: 4px;
}
// ========== 查询条件区域 ==========
.query-panel {
background: #fff;
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 12px 16px 8px;
padding: 10px 12px 2px;
.query-form {
.check-field {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
}
}
.pager-row {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
padding-top: 10px;
border-top: 1px solid #ebeef5;
margin-top: 4px;
.pager-label {
font-size: 13px;
color: #606266;
white-space: nowrap;
::v-deep .el-form-item {
margin-bottom: 8px;
}
}
}
// ========== 2/4. 表格区域 ==========
// ========== 表格区域 ==========
.table-panel {
position: relative;
background: #fff;
@@ -370,69 +205,19 @@ export default {
}
}
// ========== 3. 中间操作按钮区域 ==========
.mid-actions {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
.mid-label {
font-size: 13px;
font-weight: 600;
color: #303133;
white-space: nowrap;
margin-right: 4px;
}
}
// ========== 5. 底部按钮区域 ==========
// ========== 底部按钮区域 ==========
.footer-actions {
display: flex;
justify-content: flex-end;
align-items: center;
gap: 8px;
padding-top: 12px;
border-top: 1px solid #ebeef5;
}
// ========== 银灰色 / 蓝色边框按钮 ==========
.gray-btn {
min-width: 70px;
height: 28px;
padding: 0 14px;
font-size: 12px;
color: #000;
background: linear-gradient(180deg, #fdfdfd 0%, #ececec 50%, #dcdcdc 100%);
border: 1px solid #7a7a7a;
border-radius: 2px;
cursor: pointer;
&:hover {
background: linear-gradient(180deg, #ffffff 0%, #f4f4f4 50%, #e6e6e6 100%);
}
&:active {
background: #c8c8c8;
}
}
.blue-btn {
min-width: 110px;
height: 28px;
padding: 0 14px;
font-size: 12px;
color: #1e6fff;
background: #ecf5ff;
border: 1px solid #1e6fff;
border-radius: 2px;
cursor: pointer;
&:hover {
background: #d9ecff;
}
&:active {
background: #c6e2ff;
.selected-count {
flex: 1;
font-size: 13px;
color: #606266;
}
}
}
@@ -1,706 +0,0 @@
<template>
<el-dialog
:visible="dialogVisible"
:show-close="false"
width="96%"
top="3vh"
:close-on-click-modal="false"
:close-on-press-escape="false"
class="peidang-dialog"
@update:visible="val => dialogVisible = val"
>
<!-- ==================== 自定义标题栏 ==================== -->
<div slot="header">
<div class="peidang-titlebar">
<i class="el-icon-reading title-icon"></i>
<span class="title-text">
班次学期配档——{{ semesterName }}——{{ classLabel }}
</span>
</div>
</div>
<div class="peidang-body">
<!-- ==================== 上半部分:左右分栏 ==================== -->
<div class="upper">
<!-- 左侧 75%:配档课程编组管理表格 -->
<div class="table-area">
<el-table
ref="tableRef"
:data="displayCourses"
border
stripe
highlight-current-row
size="small"
max-height="240"
class="peidang-table"
@current-change="handleCurrentChange"
>
<el-table-column prop="name" label="科目名称" width="100" align="left" show-overflow-tooltip />
<el-table-column prop="short" label="简称" width="60" align="center" show-overflow-tooltip />
<el-table-column prop="planStart" label="计划起始周" width="60" align="center" show-overflow-tooltip />
<el-table-column prop="actualStart" label="实际起始周" width="60" align="center" show-overflow-tooltip />
<el-table-column prop="endWeek" label="结束周" width="60" align="center" show-overflow-tooltip />
<el-table-column label="连排课" width="50" align="center">
<template slot-scope="{ row }">
<el-checkbox :value="row.continuous" />
</template>
</el-table-column>
<el-table-column prop="priority" label="优先序数" width="50" align="center" show-overflow-tooltip />
<el-table-column prop="weeklyHours" label="周学时" width="50" align="center" show-overflow-tooltip />
<el-table-column prop="hours" label="学时" width="50" align="center" show-overflow-tooltip />
<el-table-column label="正课时间" width="60" align="center">
<template slot-scope="{ row }">
<el-checkbox :value="row.scheduleTime" />
</template>
</el-table-column>
<el-table-column label="已排学时" width="50" align="center">
<template slot-scope="{ row }">{{ courseStats(row).scheduled }}</template>
</el-table-column>
<el-table-column label="未排学时" width="50" align="center">
<template slot-scope="{ row }">{{ courseStats(row).remaining }}</template>
</el-table-column>
<el-table-column label="总班次任务数" width="70" align="center">
<template slot-scope="{ row }">{{ row.continuous ? 1 : 1 }}</template>
</el-table-column>
<el-table-column label="配档编组任务数" width="80" align="center">
<template slot-scope="{ row }">{{ row.continuous ? 2 : 1 }}</template>
</el-table-column>
</el-table>
</div>
<!-- 右侧 25%:配档课程属性设置区 + 优先序数上下移动 -->
<div class="prop-area">
<div class="prop-title">配档课程属性</div>
<el-form label-width="72px" size="small" class="prop-form">
<el-form-item label="周学时">
<el-input-number v-model="propForm.weeklyHours" :min="1" :max="40" controls-position="right" style="width: 100%" />
</el-form-item>
<el-form-item label="起始周次">
<el-input-number v-model="propForm.startWeek" :min="startWeek" :max="endWeek" controls-position="right" style="width: 100%" />
</el-form-item>
<el-form-item label="结束周次">
<el-input-number v-model="propForm.endWeek" :min="startWeek" :max="endWeek" controls-position="right" style="width: 100%" />
</el-form-item>
<el-form-item label="连排课">
<el-checkbox v-model="propForm.continuous">单科独进</el-checkbox>
</el-form-item>
<el-form-item label="优先序数">
<div class="priority-field">
<el-input-number v-model="propForm.priority" :min="1" controls-position="right" style="width: 100%" />
<el-button type="primary" plain icon="el-icon-arrow-up" @click="handlePriorityUp" />
<el-button type="primary" plain icon="el-icon-arrow-down" @click="handlePriorityDown" />
</div>
</el-form-item>
<el-form-item label="配档编组">
<el-input v-model="propForm.group" />
</el-form-item>
</el-form>
<el-button type="primary" class="apply-btn" @click="handleApplyProp">应用属性</el-button>
</div>
</div>
<!-- ==================== 下半部分:配档显示区域(甘特图) ==================== -->
<div class="gantt-area">
<div class="gantt-head">
<div class="gantt-label-col">
<div class="gantt-label">课程 / 周次</div>
</div>
<div class="gantt-body">
<!-- 行1:周次刻度(红色竖线为周刻度) -->
<div class="gantt-row">
<div
v-for="w in weeks"
:key="w"
class="gantt-cell week-cell"
:class="{ 'month-boundary': isMonthBoundary(w) }"
>{{ w }}</div>
</div>
<!-- 行2:本周可安排正课学时 -->
<div class="gantt-row info-row">
<div
v-for="w in weeks"
:key="w"
class="gantt-cell"
:class="{ 'month-boundary': isMonthBoundary(w) }"
>可排{{ weeklyCapacity }}</div>
</div>
<!-- 行3:已安排学时 / 剩余学时 -->
<div class="gantt-row info-row">
<div
v-for="w in weeks"
:key="w"
class="gantt-cell"
:class="{ 'month-boundary': isMonthBoundary(w) }"
>
<span class="sch">已{{ monthlyScheduled(w) }}</span>
<span class="rem">余{{ Math.max(0, weeklyCapacity - monthlyScheduled(w)) }}</span>
</div>
</div>
</div>
</div>
<!-- 课程甘特条 -->
<div class="gantt-courses">
<div v-for="course in displayCourses" :key="course.id" class="gantt-course">
<div class="gantt-label-col">
<div class="course-tag" :class="course.continuous ? 'tag-continuous' : 'tag-normal'">
<span class="course-name">{{ course.name }}({{ course.short }})</span>
<span class="course-hours">
已排{{ courseStats(course).scheduled }}
<template v-if="courseStats(course).remaining > 0">+{{ courseStats(course).remaining }}</template>
</span>
</div>
</div>
<div class="gantt-body">
<div class="gantt-row bar-row">
<div
v-for="w in weeks"
:key="w"
class="gantt-cell bar-cell"
:class="{
'month-boundary': isMonthBoundary(w),
'bar-active': w >= course.actualStart && w <= course.endWeek,
'bar-continuous': course.continuous,
'bar-normal': !course.continuous
}"
>
<span v-if="w >= course.actualStart && w <= course.endWeek" class="bar-hours">
{{ courseWeekHours(course)[w] || '' }}
</span>
</div>
</div>
</div>
</div>
</div>
<!-- 底部:本月可安排正课总学时(月刻度) -->
<div class="gantt-foot">
<div class="gantt-label-col">
<div class="gantt-label">本月正课总学时</div>
</div>
<div class="gantt-body">
<div class="gantt-row">
<div
v-for="w in weeks"
:key="w"
class="gantt-cell month-cell"
:class="{ 'month-boundary': isMonthBoundary(w) }"
>
<span v-if="bottomCells[w] && isMonthEndWeek(w)" class="month-total">
第{{ bottomCells[w].no }}月:可排{{ bottomCells[w].total }}(已排{{ bottomCells[w].scheduled }} + 余{{ bottomCells[w].remaining }})
</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ==================== 底部操作 ==================== -->
<div slot="footer">
<div class="peidang-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleConfirm">确定</el-button>
</div>
</div>
</el-dialog>
</template>
<script>
export default {
name: 'PeidangEditDialog',
props: {
visible: { type: Boolean, default: false },
/** 学年学期名称 */
semesterName: { type: String, default: '2017-2018学年第一学期' },
/** 班次标签(如 教学五系2016级专业0113班次0417) */
classLabel: { type: String, default: '教学五系2016级专业0113班次0417' }
},
data() {
return {
// ==================== 时间轴参数 ====================
/** 该班次开学起始周次 */
startWeek: 8,
/** 该班次学期结束周次 */
endWeek: 32,
/** 该班次每周可安排的正课学时时数(去除不能排课学时) */
weeklyCapacity: 40,
/** 月刻度:每 4 周为一月(8-11 第1月,12-15 第2月...),虚线分隔 */
monthBreaks: [12, 16, 20, 24, 28, 32],
// ==================== 配档课程数据 ====================
courses: [
// ---- 非连排课(按周学时安排,蓝色,每门独立一组) ----
{ id: 'p1', name: '课程科目001', short: '政治', planStart: 8, actualStart: 8, endWeek: 23, continuous: false, priority: 1, weeklyHours: 4, hours: 60, scheduleTime: true, group: '组1' },
{ id: 'p2', name: '课程科目002', short: '军事', planStart: 8, actualStart: 8, endWeek: 20, continuous: false, priority: 2, weeklyHours: 4, hours: 48, scheduleTime: true, group: '组2' },
{ id: 'p3', name: '课程科目003', short: '战术', planStart: 8, actualStart: 10, endWeek: 21, continuous: false, priority: 3, weeklyHours: 4, hours: 44, scheduleTime: true, group: '组3' },
{ id: 'p4', name: '课程科目004', short: '指挥', planStart: 8, actualStart: 12, endWeek: 22, continuous: false, priority: 4, weeklyHours: 4, hours: 40, scheduleTime: true, group: '组4' },
// ---- 连排课(单科独进,红色,共为一组) ----
{ id: 'p5', name: '课程科目005', short: '综合演练', planStart: 8, actualStart: 8, endWeek: 13, continuous: true, priority: 5, weeklyHours: 20, hours: 100, scheduleTime: true, group: '连排组' },
{ id: 'p6', name: '课程科目006', short: '野营拉练', planStart: 14, actualStart: 14, endWeek: 16, continuous: true, priority: 6, weeklyHours: 24, hours: 48, scheduleTime: true, group: '连排组' }
],
// ==================== 表格选中与属性设置 ====================
currentCourse: null,
propForm: {
weeklyHours: 4,
startWeek: 8,
endWeek: 16,
continuous: false,
priority: 1,
group: ''
}
}
},
computed: {
dialogVisible: {
get() {
return this.visible
},
set(val) {
this.$emit('update:visible', val)
}
},
/** 周序列:8 ~ 32 */
weeks() {
const arr = []
for (let w = this.startWeek; w <= this.endWeek; w++) arr.push(w)
return arr
},
/** 月份信息:每月包含的周次与可安排正课学时 */
months() {
const list = []
let mNo = 1
let cur = this.startWeek
while (cur <= this.endWeek) {
const next = this.monthBreaks.find((b) => b > cur)
const mEnd = Math.min((next === undefined ? this.endWeek + 1 : next) - 1, this.endWeek)
list.push({ no: mNo, start: cur, end: mEnd, total: (mEnd - cur + 1) * this.weeklyCapacity })
cur = mEnd + 1
mNo++
}
return list
},
/** 底部每月汇总行数据:week -> 该月信息(仅月首格展示文字) */
bottomCells() {
const map = {}
this.weeks.forEach((w) => {
const m = this.months.find((item) => w >= item.start && w <= item.end)
if (!m) return
map[w] = {
no: m.no,
total: m.total,
scheduled: this.monthlyScheduled(w),
remaining: m.total - this.monthlyScheduled(w)
}
})
return map
},
/** 表格展示顺序:非连排课在前,连排课在后;组内按实际起始周、优先序数排序 */
displayCourses() {
return [...this.courses].sort((a, b) => {
if (a.continuous !== b.continuous) return a.continuous ? 1 : -1
if (a.actualStart !== b.actualStart) return a.actualStart - b.actualStart
return a.priority - b.priority
})
}
},
watch: {
visible(val) {
if (!val) return
this.$nextTick(() => {
if (this.$refs.tableRef) {
this.$refs.tableRef.setCurrentRow(this.displayCourses[0])
}
this.currentCourse = this.displayCourses[0] || null
this.syncPropForm()
})
}
},
methods: {
/** 是否为月边界列(左侧画虚线) */
isMonthBoundary(week) {
return this.monthBreaks.includes(week)
},
/** 是否为某月的结束周(仅月结束列展示文字) */
isMonthEndWeek(week) {
return this.months.some((m) => m.end === week)
},
/** 某周所有课程已安排学时之和 */
monthlyScheduled(week) {
return this.courses.reduce((sum, c) => {
const map = this.courseWeekHours(c)
return sum + (map[week] !== undefined ? map[week] : 0)
}, 0)
},
/** 每门课程按周分布的安排学时 */
courseWeekHours(course) {
const map = {}
let remaining = course.hours
let w = course.actualStart
while (remaining > 0 && w <= course.endWeek) {
const h = Math.min(course.weeklyHours, remaining)
map[w] = h
remaining -= h
w++
}
return map
},
/** 课程计算属性:已排学时 / 未排学时 */
courseStats(course) {
const map = this.courseWeekHours(course)
const scheduled = Object.keys(map).reduce((sum, k) => sum + map[k], 0)
return { scheduled, remaining: Math.max(0, course.hours - scheduled) }
},
handleCurrentChange(row) {
this.currentCourse = row
this.syncPropForm()
},
/** 属性设置表单同步(右侧) */
syncPropForm() {
if (!this.currentCourse) return
this.propForm.weeklyHours = this.currentCourse.weeklyHours
this.propForm.startWeek = this.currentCourse.actualStart
this.propForm.endWeek = this.currentCourse.endWeek
this.propForm.continuous = this.currentCourse.continuous
this.propForm.priority = this.currentCourse.priority
this.propForm.group = this.currentCourse.group
},
/** 属性设置保存:应用所选课程的配档属性 */
handleApplyProp() {
if (!this.currentCourse) {
this.$message.warning('请先在表格中选择一门课程')
return
}
const row = this.currentCourse
row.weeklyHours = this.propForm.weeklyHours
row.actualStart = this.propForm.startWeek
row.endWeek = this.propForm.endWeek
row.continuous = this.propForm.continuous
row.priority = this.propForm.priority
row.group = this.propForm.group
this.$message.success('配档属性已应用(前端演示)')
},
/** 优先序数上移:优先序数减 1 */
handlePriorityUp() {
if (!this.currentCourse) {
this.$message.warning('请先在表格中选择一门课程')
return
}
if (this.currentCourse.priority <= 1) {
this.$message.info('已是最高优先序数')
return
}
this.currentCourse.priority -= 1
this.syncPropForm()
},
/** 优先序数下移:优先序数加 1 */
handlePriorityDown() {
if (!this.currentCourse) {
this.$message.warning('请先在表格中选择一门课程')
return
}
this.currentCourse.priority += 1
this.syncPropForm()
},
// ==================== 底部操作 ====================
handleConfirm() {
this.$emit('confirm')
this.dialogVisible = false
}
}
}
</script>
<style scoped lang="scss">
.peidang-dialog {
// ========== 自定义标题栏 ==========
::v-deep .el-dialog__header {
margin: 0;
padding: 0;
}
.peidang-titlebar {
display: flex;
align-items: center;
gap: 8px;
height: 40px;
padding: 0 14px;
background: linear-gradient(90deg, #0a246a 0%, #a6caf0 100%);
color: #fff;
.title-icon {
font-size: 18px;
color: #fff;
}
.title-text {
font-size: 14px;
font-weight: 600;
color: #fff;
letter-spacing: 0.5px;
}
}
::v-deep .el-dialog__body {
padding: 12px;
}
::v-deep .el-dialog__footer {
padding: 8px 12px;
border-top: 1px solid #ebeef5;
}
.peidang-footer {
display: flex;
justify-content: flex-end;
gap: 8px;
}
}
.peidang-body {
display: flex;
flex-direction: column;
gap: 10px;
// ========== 上半部分:左右分栏 ==========
.upper {
display: flex;
gap: 10px;
// 左侧 75%:表格
.table-area {
flex: 3;
min-width: 0;
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 6px;
overflow: hidden;
.peidang-table {
width: 100%;
}
}
// 右侧 25%:属性设置
.prop-area {
flex: 1;
min-width: 240px;
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 10px 12px;
.prop-title {
font-size: 14px;
font-weight: 600;
color: #303133;
margin-bottom: 8px;
padding-bottom: 6px;
border-bottom: 1px solid #ebeef5;
}
.prop-form {
.priority-field {
display: flex;
align-items: center;
gap: 4px;
width: 100%;
.el-button {
flex-shrink: 0;
margin-left: 0;
}
}
}
.apply-btn {
width: 100%;
margin-top: 4px;
}
}
}
// ========== 下半部分:配档显示区域 ==========
.gantt-area {
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 8px;
overflow: auto;
max-height: 320px;
.gantt-head,
.gantt-course,
.gantt-foot {
display: flex;
align-items: stretch;
min-width: 860px;
}
.gantt-label-col {
flex-shrink: 0;
width: 210px;
display: flex;
align-items: center;
.gantt-label {
font-size: 12px;
color: #606266;
font-weight: 600;
white-space: nowrap;
}
}
.gantt-body {
flex: 1;
display: flex;
flex-direction: column;
}
.gantt-row {
display: flex;
.gantt-cell {
flex: 1;
min-width: 34px;
height: 24px;
line-height: 24px;
text-align: center;
font-size: 11px;
color: #303133;
border-right: 1px solid #ebeef5;
border-bottom: 1px solid #ebeef5;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
// 周刻度:顶部红色竖线
.week-cell {
font-weight: 600;
color: #f56c6c;
border-top: 2px solid #f56c6c;
background: #fff5f5;
}
.info-row .gantt-cell {
height: 20px;
line-height: 20px;
.sch {
color: #1e6fff;
}
.rem {
color: #e6a23c;
margin-left: 2px;
}
}
// 月刻度虚线
.month-boundary {
border-left: 1px dashed #909399;
}
// 课程条
.bar-cell {
height: 26px;
position: relative;
.bar-hours {
font-size: 11px;
font-weight: 600;
}
&.bar-active {
&.bar-normal {
background: #409eff;
color: #fff;
.bar-hours {
color: #fff;
}
}
&.bar-continuous {
background: #f56c6c;
color: #fff;
.bar-hours {
color: #fff;
}
}
}
}
.month-cell {
height: 22px;
line-height: 22px;
.month-total {
font-size: 11px;
color: #606266;
font-weight: 600;
background: #f4f4f5;
border: 1px solid #dcdfe6;
border-radius: 2px;
padding: 0 4px;
white-space: nowrap;
}
}
}
.gantt-course {
.gantt-label-col {
align-items: center;
.course-tag {
display: flex;
flex-direction: column;
justify-content: center;
gap: 1px;
padding: 2px 6px;
border-radius: 3px;
font-size: 11px;
&.tag-normal {
background: #ecf5ff;
border: 1px solid #a0cfff;
color: #337ecc;
}
&.tag-continuous {
background: #fef0f0;
border: 1px solid #fbc4c4;
color: #d63031;
}
.course-name {
font-weight: 600;
white-space: nowrap;
}
.course-hours {
font-size: 10px;
white-space: nowrap;
}
}
}
}
.gantt-foot {
.gantt-label-col {
align-items: center;
}
}
}
}
</style>
@@ -1,369 +1,591 @@
<template>
<div class="app-container shift-semester-page">
<!-- ==================== 1. 学年学期标题 ==================== -->
<div class="list-title">{{ semesterName }}</div>
<!-- ==================== 页面标题 ==================== -->
<div class="page-title">班次学期信息管理</div>
<!-- ==================== 2. 校历 / 预设日期信息区 ==================== -->
<!-- ==================== 1. 查询条件区域 ==================== -->
<el-card shadow="never" class="search-card">
<el-form class="search-form">
<div class="date-group">
<span class="date-group-label">校历</span>
<el-form-item label="开学日期">
<el-date-picker v-model="semester.kxrq" type="date" format="yyyy年M月d日" value-format="yyyy-MM-dd" style="width: 160px" />
</el-form-item>
<el-form-item label="结束日期">
<el-date-picker v-model="semester.jsrq" type="date" format="yyyy年M月d日" value-format="yyyy-MM-dd" style="width: 160px" />
</el-form-item>
<el-form-item label="学期周数">
<el-input-number v-model="semester.weeks" :min="1" controls-position="right" style="width: 90px" />
</el-form-item>
</div>
<div class="date-group">
<span class="date-group-label">预设</span>
<el-form-item label="开学日期">
<el-date-picker v-model="preset.kxrq" type="date" format="yyyy年M月d日" value-format="yyyy-MM-dd" style="width: 160px" />
</el-form-item>
<el-form-item label="结束日期">
<el-date-picker v-model="preset.jsrq" type="date" format="yyyy年M月d日" value-format="yyyy-MM-dd" style="width: 160px" />
</el-form-item>
<el-form-item label="学期周数">
<el-input-number v-model="preset.weeks" :min="1" controls-position="right" style="width: 90px" />
</el-form-item>
<el-button class="absorb-btn" type="primary" plain @click="handleAbsorbDate">吸取日期</el-button>
</div>
<el-form :inline="true" :model="searchForm" class="search-form">
<el-form-item label="班次编号">
<el-input
v-model="searchForm.xydbh"
placeholder="请输入班次编号(模糊)"
clearable
style="width: 220px"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="年度">
<el-select v-model="filterYear" placeholder="全部年度" clearable style="width: 140px" @change="handleYearChange">
<el-option v-for="y in yearFilterOptions" :key="y" :label="`${y}年`" :value="y" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
<el-button icon="el-icon-refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- ==================== 3. 工具栏按钮区 ==================== -->
<!-- ==================== 2. 工具栏按钮区 ==================== -->
<div class="list-toolbar">
<div class="left-group">
<!-- 新添:下拉列出可新建学期信息的班次 -->
<el-dropdown trigger="click" @command="handleNewCommand">
<el-button type="primary">
<i class="el-icon-plus"></i>
<span>新添</span>
<i class="el-icon-arrow-down el-icon--right"></i>
</el-button>
<el-dropdown-menu slot="dropdown" class="new-class-menu">
<el-dropdown-item v-for="item in newableClasses" :key="item.bc" :command="item">
{{ item.bc }}({{ item.xkzy }}、{{ item.nj }})
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<el-button type="primary" plain @click="handleAction('编辑')">编辑</el-button>
<el-button type="primary" plain @click="handleQuickSetDates">快速设定学期日期</el-button>
<el-button type="danger" @click="handleAction('删除')">删除</el-button>
<el-button type="primary" plain @click="handleEditCalendar">编辑班历</el-button>
<el-button type="primary" plain @click="handleAction('自动生成教学计划')">自动生成教学计划</el-button>
<el-button type="primary" plain @click="handleAction('教学计划管理')">教学计划管理</el-button>
<!-- 序号排序:带下拉箭头,点击弹出排序方式 -->
<el-dropdown trigger="click" @command="handleSortCommand">
<el-button type="primary" plain>
<span>{{ sortLabel }}</span>
<i class="el-icon-arrow-down el-icon--right"></i>
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="xh">序号排序</el-dropdown-item>
<el-dropdown-item command="bw">波次排序</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<el-button type="primary" plain @click="handleMerge">预设合班</el-button>
<el-button type="primary" plain @click="handleAction('预设拆班')">预设拆班</el-button>
<el-button type="primary" plain @click="handlePeidangEdit">配档编辑</el-button>
<el-button type="primary" icon="el-icon-plus" @click="handleBatchAdd">批量创建班次学期</el-button>
<el-button type="primary" plain icon="el-icon-document-add" @click="handleAdd">新增</el-button>
<el-button type="primary" plain icon="el-icon-edit" :disabled="!currentRow" @click="handleEdit">编辑</el-button>
<el-button type="danger" plain icon="el-icon-delete" :disabled="!selection.length" @click="handleDelete">删除所选</el-button>
<el-button type="primary" plain icon="el-icon-date" :disabled="!selection.length" @click="handleQuickSetDates">快速设定学期日期</el-button>
<el-button type="primary" plain icon="el-icon-notebook-2" :disabled="!selection.length" @click="handleSetJxrw">批量设置教学任务</el-button>
</div>
<div class="right-group">
<el-button type="success" plain icon="el-icon-calendar" :disabled="!currentRow" @click="handleEditCalendar">编辑班历</el-button>
</div>
</div>
<!-- ==================== 4. 班次学期信息列表 ==================== -->
<!-- ==================== 3. 班次学期信息列表 ==================== -->
<el-card shadow="never" class="table-card">
<el-table
ref="tableRef"
v-loading="loading"
:data="displayData"
border
stripe
highlight-current-row
:span-method="spanMethod"
:row-class-name="rowClassName"
:header-cell-class-name="headerCellClassName"
size="small"
max-height="480"
class="class-table"
@selection-change="handleSelectionChange"
@current-change="handleCurrentChange"
>
<el-table-column prop="xzdw" label="行政队别" width="90" align="center" show-overflow-tooltip />
<el-table-column prop="xh" label="班次序号" width="70" align="center" />
<el-table-column prop="bc" label="班级" width="70" align="center" show-overflow-tooltip />
<el-table-column prop="rs" label="人数" width="55" align="center" show-overflow-tooltip />
<el-table-column prop="nj" label="年级" width="85" align="center" show-overflow-tooltip />
<el-table-column prop="xkzy" label="学科专业" width="85" align="center" show-overflow-tooltip />
<el-table-column prop="kxrq" label="开学日期" width="90" align="center" show-overflow-tooltip />
<el-table-column prop="jsrq" label="结束日期" width="90" align="center" show-overflow-tooltip />
<el-table-column prop="xqcc" label="学期第次" width="80" align="center" show-overflow-tooltip />
<el-table-column prop="zyjs" label="专用教室" width="80" align="center" show-overflow-tooltip />
<el-table-column prop="jxrw" label="教学任务" min-width="160" align="left" show-overflow-tooltip />
<el-table-column prop="zs" label="周数" width="55" align="center" show-overflow-tooltip />
<el-table-column prop="kcrws" label="总课程数" width="80" align="center" show-overflow-tooltip />
<el-table-column prop="kczs" label="总课时数" width="80" align="center" show-overflow-tooltip />
<el-table-column label="周学时" width="80" align="center">
<template slot-scope="{ row }">{{ row.zxs.toFixed(2) }}</template>
<template slot="empty">
<span>无数据!</span>
</template>
<el-table-column type="selection" width="44" align="center" />
<el-table-column type="index" label="序号" width="55" align="center" />
<el-table-column prop="xydbh" label="班次编号" min-width="150" align="left" header-align="center" show-overflow-tooltip />
<el-table-column prop="nd" label="年度" width="80" align="center" show-overflow-tooltip />
<el-table-column label="学期第次" width="90" align="center">
<template slot-scope="{ row }">{{ xqdcLabel(row.xqdc) }}</template>
</el-table-column>
<el-table-column prop="kxrq" label="开学日期" width="110" align="center" :formatter="fmtDate" />
<el-table-column prop="jsrq" label="结束日期" width="110" align="center" :formatter="fmtDate" />
<el-table-column prop="zyjsbh" label="专用教室" width="100" align="center" show-overflow-tooltip />
<el-table-column label="教学任务" min-width="160" align="left" header-align="center" show-overflow-tooltip>
<template slot-scope="{ row }">{{ taskName(row.jxrwbh) }}</template>
</el-table-column>
<el-table-column label="开放教员排课" width="110" align="center">
<template slot-scope="{ row }">
<el-tag v-if="Number(row.kfjypk) === 1" type="success" size="small">开放</el-tag>
<el-tag v-else type="info" size="small">关闭</el-tag>
</template>
</el-table-column>
<el-table-column prop="bz" label="备注" min-width="120" align="left" header-align="center" show-overflow-tooltip />
<el-table-column label="操作" width="200" align="center" fixed="right">
<template slot-scope="{ row }">
<el-button type="text" size="small" @click="handleEdit(row)">编辑</el-button>
<el-button type="text" size="small" @click="handleEditCalendar(row)">班历</el-button>
<el-button type="text" size="small" class="text-danger" @click="handleDeleteRow(row)">删除</el-button>
</template>
</el-table-column>
<el-table-column prop="zxf" label="总学分" width="70" align="center" show-overflow-tooltip />
</el-table>
<el-pagination
class="list-pagination"
:current-page="pageNum"
:page-size="pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handlePageChange"
/>
</el-card>
<!-- ==================== 教学班次学期信息明细弹窗(新添) ==================== -->
<!-- ==================== 班次学期新增/编辑弹窗 ==================== -->
<ClassSemesterDetailDialog
:visible="detailVisible"
:class-info="selectedNewClass"
:semester-name="semesterName"
:mode="detailMode"
:semester="detailSemester"
:team-info="detailTeamInfo"
:year-options="yearOptions"
:task-options="taskOptions"
@update:visible="val => detailVisible = val"
@confirm="handleDetailConfirm"
@submit="handleDetailSubmit"
/>
<!-- ==================== 班历设置弹窗(编辑班历) ==================== -->
<!-- ==================== 班历管理弹窗 ==================== -->
<ClassCalendarDialog
:visible="calendarVisible"
:bc="currentRow ? currentRow.bc : ''"
:semester-name="semesterName"
:semester="calendarSemester"
:semester-options="semesterOptions"
@update:visible="val => calendarVisible = val"
/>
<!-- ==================== 预设合班弹窗 ==================== -->
<ClassTeamSelectDialog
:visible="teamVisible"
@update:visible="val => teamVisible = val"
@confirm="handleTeamConfirm"
/>
<!-- ==================== 批量创建班次学期弹窗 ==================== -->
<el-dialog
:visible="batchAddVisible"
title="批量创建班次学期"
width="520px"
:close-on-click-modal="false"
@update:visible="val => batchAddVisible = val"
>
<el-form ref="batchAddFormRef" :model="batchAddForm" :rules="batchAddRules" label-width="100px">
<el-form-item label="年度" prop="nd">
<el-select v-model="batchAddForm.nd" placeholder="请选择年度" style="width: 100%">
<el-option v-for="y in yearOptions" :key="y" :label="`${y}年`" :value="y" />
</el-select>
</el-form-item>
<el-form-item label="学期" prop="xqdc">
<el-select v-model="batchAddForm.xqdc" placeholder="请选择学期" style="width: 100%">
<el-option label="春季学期" value="春季学期" />
<el-option label="夏季学期" value="夏季学期" />
<el-option label="秋季学期" value="秋季学期" />
</el-select>
</el-form-item>
<el-form-item label="开始日期" prop="startTime">
<el-date-picker v-model="batchAddForm.startTime" type="date" value-format="yyyy-MM-dd" placeholder="请选择开始日期" style="width: 100%" />
</el-form-item>
<el-form-item label="结束日期" prop="endTime">
<el-date-picker v-model="batchAddForm.endTime" type="date" value-format="yyyy-MM-dd" placeholder="请选择结束日期" style="width: 100%" />
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="batchAddVisible = false">取消</el-button>
<el-button type="primary" :loading="saving" @click="handleBatchAddSubmit">确定</el-button>
</div>
</el-dialog>
<!-- ==================== 班次学期配档编辑弹窗 ==================== -->
<PeidangEditDialog
:visible="peidangVisible"
:semester-name="semesterName"
:class-label="peidangClassLabel"
@update:visible="val => peidangVisible = val"
@confirm="handleAction('配档编辑')"
/>
<!-- ==================== 快速设定学期日期弹窗 ==================== -->
<el-dialog
:visible="dateRangeVisible"
title="快速设定学期日期"
width="480px"
:close-on-click-modal="false"
@update:visible="val => dateRangeVisible = val"
>
<el-alert
type="info"
:closable="false"
show-icon
:title="`将对选中的 ${selection.length} 条班次学期统一设定开学/结束日期`"
style="margin-bottom: 16px"
/>
<el-form ref="dateRangeFormRef" :model="dateRangeForm" :rules="dateRangeRules" label-width="100px">
<el-form-item label="开学日期" prop="kxrq">
<el-date-picker v-model="dateRangeForm.kxrq" type="date" value-format="yyyy-MM-dd" placeholder="请选择开学日期" style="width: 100%" />
</el-form-item>
<el-form-item label="结束日期" prop="jsrq">
<el-date-picker v-model="dateRangeForm.jsrq" type="date" value-format="yyyy-MM-dd" placeholder="请选择结束日期" style="width: 100%" />
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="dateRangeVisible = false">取消</el-button>
<el-button type="primary" :loading="saving" @click="handleDateRangeSubmit">确定</el-button>
</div>
</el-dialog>
<!-- ==================== 批量设置教学任务弹窗 ==================== -->
<el-dialog
:visible="jxrwVisible"
title="批量设置教学任务"
width="480px"
:close-on-click-modal="false"
@update:visible="val => jxrwVisible = val"
>
<el-alert
type="info"
:closable="false"
show-icon
:title="`将对选中的 ${selection.length} 条班次学期统一设置教学任务`"
style="margin-bottom: 16px"
/>
<el-form ref="jxrwFormRef" :model="jxrwForm" :rules="jxrwRules" label-width="100px">
<el-form-item label="教学任务" prop="jxrwbh">
<el-select v-model="jxrwForm.jxrwbh" placeholder="请选择教学任务" filterable style="width: 100%">
<el-option v-for="opt in taskOptions" :key="opt.bh" :label="opt.rwmc" :value="opt.bh" />
</el-select>
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="jxrwVisible = false">取消</el-button>
<el-button type="primary" :loading="saving" @click="handleJxrwSubmit">确定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import ClassSemesterDetailDialog from './ClassSemesterDetailDialog.vue'
import ClassCalendarDialog from './ClassCalendarDialog.vue'
import ClassTeamSelectDialog from './ClassTeamSelectDialog.vue'
import PeidangEditDialog from './PeidangEditDialog.vue'
import { listSemester, allSemester, addSemester, updateSemester, delSemester, batchDeleteSemester, batchUpdateDateRange, batchUpdateJxrwbh, batchAddFromElective } from '@/api/studentRecords/semester'
import { listAllSemester } from '@/api/teachBusiness/semester'
import { listTeachingTask } from '@/api/teachBusiness/teachingTask'
export default {
name: 'ShiftSemester',
components: {
ClassSemesterDetailDialog,
ClassCalendarDialog,
ClassTeamSelectDialog,
PeidangEditDialog
ClassCalendarDialog
},
data() {
return {
// ==================== 1. 学年学期标题区 ====================
semesterName: '2017-2018学年第一学期',
loading: false,
saving: false,
// ==================== 2. 校历 / 预设日期信息区 ====================
/** 校历日期 */
semester: {
kxrq: '2017-07-08', // 校历开学日期
jsrq: '2018-02-11', // 校历结束日期
weeks: 32 // 学期周数
},
/** 预设(班次)日期 */
preset: {
kxrq: '2017-07-08', // 预设开学日期
jsrq: '2018-02-11', // 预设结束日期
weeks: 32 // 预设学期周数
},
// ==================== 查询条件 ====================
searchForm: { xydbh: '' },
filterYear: undefined,
yearFilterOptions: [],
// ==================== 3. 工具栏按钮区 ====================
/** 可新建学期信息的班次 */
newableClasses: [
{ bc: '班次0633', xkzy: '专业209', nj: '2017级' },
{ bc: '班次0634', xkzy: '专业209', nj: '2017级' },
{ bc: '班次0709', xkzy: '专业205', nj: '2017级' },
{ bc: '班次0710', xkzy: '专业205', nj: '2017级' },
{ bc: '班次0901', xkzy: '专业301', nj: '2017级' }
],
/** 教学班次学期信息明细弹窗(新添) */
detailVisible: false,
selectedNewClass: null,
/** 班历设置弹窗(编辑班历) */
calendarVisible: false,
// ==================== 列表与分页 ====================
tableData: [],
selection: [],
currentRow: null,
pageNum: 1,
pageSize: 10,
total: 0,
/** 预设合班弹窗 */
teamVisible: false,
// ==================== 下拉数据 ====================
/** 年度下拉(来自 /semester/all) */
yearOptions: [],
/** 教学任务下拉({ bh, rwmc }) */
taskOptions: [],
/** 全部班次学期(用于班历复制目标选择) */
semesterOptions: [],
/** 配档编辑弹窗(班次学期配档)显示状态 */
peidangVisible: false,
/** 配档编辑弹窗标题(班次学期配档——...——班次) */
peidangClassLabel: '',
// ==================== 班次学期新增/编辑 ====================
detailVisible: false,
detailMode: 'add',
detailSemester: null,
detailTeamInfo: null,
/** 排序方式:xh=序号排序 / bw=波次排序 */
sortType: 'xh',
// ==================== 班历管理 ====================
calendarVisible: false,
calendarSemester: null,
/** 波次背景色(同一波次使用同一颜色连在一起显示) */
bwColors: [
'#e3f2fd', // 浅蓝
'#e8f5e9', // 浅绿
'#fff8e1', // 浅黄
'#f3e5f5', // 浅紫
'#e0f2f1' // 浅青
],
// ==================== 批量创建班次学期 ====================
batchAddVisible: false,
batchAddForm: { nd: undefined, xqdc: '', startTime: '', endTime: '' },
batchAddRules: {
nd: [{ required: true, message: '请选择年度', trigger: 'change' }],
xqdc: [{ required: true, message: '请选择学期', trigger: 'change' }],
startTime: [{ required: true, message: '请选择开始日期', trigger: 'change' }],
endTime: [{ required: true, message: '请选择结束日期', trigger: 'change' }]
},
// ==================== 4. 班次学期信息列表 ====================
tableData: [
// ---- 学员1队(跨 10 行) ----
{ xzdw: '学员1队', xh: 1, bw: 1, bc: '0150', rs: 40, nj: '2017级秋', xkzy: '专业5', kxrq: '2017-09-03', jsrq: '2018-02-05', xqcc: '第1学期', zyjs: '1-307', jxrw: '2017年下半年教学任务', zs: 24, kcrws: 14, kczs: 512, zxs: 21.33, zxf: 40 },
{ xzdw: '学员1队', xh: 2, bw: 1, bc: '0151', rs: 40, nj: '2017级秋', xkzy: '专业5', kxrq: '2017-09-03', jsrq: '2018-02-05', xqcc: '第1学期', zyjs: '1-307', jxrw: '2017年下半年教学任务', zs: 24, kcrws: 14, kczs: 512, zxs: 21.33, zxf: 40 },
{ xzdw: '学员1队', xh: 3, bw: 1, bc: '0152', rs: 40, nj: '2017级秋', xkzy: '专业5', kxrq: '2017-09-03', jsrq: '2018-02-05', xqcc: '第1学期', zyjs: '1-308', jxrw: '2017年下半年教学任务', zs: 24, kcrws: 14, kczs: 512, zxs: 21.33, zxf: 40 },
{ xzdw: '学员1队', xh: 4, bw: 1, bc: '0153', rs: 40, nj: '2017级秋', xkzy: '专业5', kxrq: '2017-09-03', jsrq: '2018-02-05', xqcc: '第1学期', zyjs: '1-308', jxrw: '2017年下半年教学任务', zs: 24, kcrws: 14, kczs: 512, zxs: 21.33, zxf: 40 },
{ xzdw: '学员1队', xh: 5, bw: 1, bc: '0154', rs: 42, nj: '2017级秋', xkzy: '专业5', kxrq: '2017-09-03', jsrq: '2018-02-05', xqcc: '第1学期', zyjs: '1-309', jxrw: '2017年下半年教学任务', zs: 24, kcrws: 14, kczs: 528, zxs: 22.0, zxf: 42 },
{ xzdw: '学员1队', xh: 6, bw: 1, bc: '0155', rs: 42, nj: '2017级秋', xkzy: '专业5', kxrq: '2017-09-03', jsrq: '2018-02-05', xqcc: '第1学期', zyjs: '1-309', jxrw: '2017年下半年教学任务', zs: 24, kcrws: 14, kczs: 528, zxs: 22.0, zxf: 42 },
{ xzdw: '学员1队', xh: 7, bw: 1, bc: '0156', rs: 42, nj: '2017级秋', xkzy: '专业5', kxrq: '2017-09-03', jsrq: '2018-02-05', xqcc: '第1学期', zyjs: '1-310', jxrw: '2017年下半年教学任务', zs: 24, kcrws: 15, kczs: 540, zxs: 22.5, zxf: 43 },
{ xzdw: '学员1队', xh: 8, bw: 1, bc: '0157', rs: 38, nj: '2017级秋', xkzy: '专业5', kxrq: '2017-09-03', jsrq: '2018-02-05', xqcc: '第1学期', zyjs: '1-310', jxrw: '2017年下半年教学任务', zs: 24, kcrws: 15, kczs: 540, zxs: 22.5, zxf: 43 },
{ xzdw: '学员1队', xh: 9, bw: 1, bc: '0158', rs: 38, nj: '2017级秋', xkzy: '专业5', kxrq: '2017-09-03', jsrq: '2018-02-05', xqcc: '第1学期', zyjs: '1-311', jxrw: '2017年下半年教学任务', zs: 24, kcrws: 14, kczs: 512, zxs: 21.33, zxf: 40 },
{ xzdw: '学员1队', xh: 10, bw: 1, bc: '0159', rs: 38, nj: '2017级秋', xkzy: '专业5', kxrq: '2017-09-03', jsrq: '2018-02-05', xqcc: '第1学期', zyjs: '1-311', jxrw: '2017年下半年教学任务', zs: 24, kcrws: 14, kczs: 512, zxs: 21.33, zxf: 40 },
// ---- 教学五系(跨 2 行) ----
{ xzdw: '教学五系', xh: 11, bw: 2, bc: '0201', rs: 30, nj: '2016级', xkzy: '专业113', kxrq: '2017-08-24', jsrq: '2018-02-05', xqcc: '第3学期', zyjs: '6-202', jxrw: '2017年下半年教学任务', zs: 25, kcrws: 12, kczs: 480, zxs: 19.2, zxf: 36 },
{ xzdw: '教学五系', xh: 12, bw: 2, bc: '0202', rs: 30, nj: '2016级', xkzy: '专业113', kxrq: '2017-08-24', jsrq: '2018-02-05', xqcc: '第3学期', zyjs: '6-202', jxrw: '2017年下半年教学任务', zs: 25, kcrws: 12, kczs: 480, zxs: 19.2, zxf: 36 },
// ---- 教学七系(跨 3 行) ----
{ xzdw: '教学七系', xh: 13, bw: 3, bc: '0203', rs: 36, nj: '2016级', xkzy: '专业115', kxrq: '2017-08-24', jsrq: '2018-02-05', xqcc: '第3学期', zyjs: '8-007', jxrw: '2017年下半年教学任务', zs: 25, kcrws: 13, kczs: 520, zxs: 20.8, zxf: 38 },
{ xzdw: '教学七系', xh: 14, bw: 3, bc: '0204', rs: 36, nj: '2016级', xkzy: '专业115', kxrq: '2017-08-24', jsrq: '2018-02-05', xqcc: '第3学期', zyjs: '8-007', jxrw: '2017年下半年教学任务', zs: 25, kcrws: 13, kczs: 520, zxs: 20.8, zxf: 38 },
{ xzdw: '教学七系', xh: 15, bw: 3, bc: '0205', rs: 36, nj: '2016级', xkzy: '专业115', kxrq: '2017-08-24', jsrq: '2018-02-05', xqcc: '第3学期', zyjs: '8-008', jxrw: '2017年下半年教学任务', zs: 25, kcrws: 13, kczs: 520, zxs: 20.8, zxf: 38 }
]
// ==================== 快速设定学期日期 ====================
dateRangeVisible: false,
dateRangeForm: { kxrq: '', jsrq: '' },
dateRangeRules: {
kxrq: [{ required: true, message: '请选择开学日期', trigger: 'change' }],
jsrq: [{ required: true, message: '请选择结束日期', trigger: 'change' }]
},
// ==================== 批量设置教学任务 ====================
jxrwVisible: false,
jxrwForm: { jxrwbh: undefined },
jxrwRules: {
jxrwbh: [{ required: true, message: '请选择教学任务', trigger: 'change' }]
}
}
},
computed: {
/** 排序方式标签 */
sortLabel() {
return this.sortType === 'bw' ? '波次排序' : '序号排序'
},
/** 根据排序方式展示列表:波次排序时按波次分组连排 */
/** 本地按年度过滤(后端 /semester/list 仅支持 xydbh 模糊筛选) */
displayData() {
if (this.sortType === 'bw') {
return [...this.tableData].sort((a, b) => a.bw - b.bw || a.xh - b.xh)
}
return this.tableData
if (!this.filterYear) return this.tableData
return this.tableData.filter(item => String(item.nd) === String(this.filterYear))
}
},
mounted() {
// 默认选中第一行 班次0150(高亮)
this.$nextTick(() => {
if (this.tableData.length && this.$refs.tableRef) {
this.$refs.tableRef.setCurrentRow(this.tableData[0])
}
})
this.loadYearOptions()
this.loadTaskOptions()
this.loadSemesterOptions()
this.loadList()
},
methods: {
// ==================== 2. 校历 / 预设日期 ====================
/** 吸取日期:将校历日期复制到预设日期 */
handleAbsorbDate() {
this.preset.kxrq = this.semester.kxrq
this.preset.jsrq = this.semester.jsrq
this.preset.weeks = this.semester.weeks
this.$message.success('已吸取校历日期到预设日期')
/* ==================== 格式化 ==================== */
fmtDate(val) {
if (!val) return '-'
return String(val).slice(0, 10)
},
xqdcLabel(val) {
if (val === 1 || val === '1') return '第1学期'
if (val === 2 || val === '2') return '第2学期'
if (val === 3 || val === '3') return '第3学期'
return val !== undefined && val !== null && val !== '' ? `第${val}学期` : '-'
},
/** 教学任务编号 → 名称 */
taskName(bh) {
if (!bh) return '-'
const opt = this.taskOptions.find(o => o.bh === bh)
return opt ? opt.rwmc : bh
},
cleanPayload(obj) {
const payload = {}
Object.keys(obj).forEach(key => {
const value = obj[key]
if (value !== '' && value !== null && value !== undefined) {
payload[key] = value
}
})
return payload
},
/** 快速设定学期日期 */
handleQuickSetDates() {
this.$message.success('快速设定学期日期(前端演示)')
/* ==================== 下拉数据加载 ==================== */
loadYearOptions() {
listAllSemester().then(res => {
const list = (res && res.data) || []
const years = list.map(x => x.nd).filter(v => v !== undefined && v !== null && v !== '')
this.yearOptions = [...new Set(years)].sort((a, b) => Number(a) - Number(b))
}).catch(() => {
this.yearOptions = []
})
},
loadTaskOptions() {
listTeachingTask({ pageNum: 1, pageSize: 1000 }).then(res => {
const list = (res && res.data && res.data.records) || []
this.taskOptions = list.filter(o => o.bh)
}).catch(() => {
this.taskOptions = []
})
},
loadSemesterOptions() {
allSemester().then(res => {
this.semesterOptions = (res && res.data) || []
}).catch(() => {
this.semesterOptions = []
})
},
// ==================== 3. 工具栏按钮区 ====================
/** 通用操作(前端演示) */
handleAction(name) {
this.$message.success(`${name}(前端演示)`)
/* ==================== 列表加载 ==================== */
loadList() {
this.loading = true
const query = this.cleanPayload({
pageNum: this.pageNum,
pageSize: this.pageSize,
xydbh: this.searchForm.xydbh
})
listSemester(query).then(res => {
const data = (res && res.data) || {}
this.tableData = data.records || []
this.total = data.total || 0
this.buildYearFilterOptions()
this.loading = false
}).catch(() => {
this.tableData = []
this.total = 0
this.loading = false
})
},
/** 新添:从下拉选择班次后打开明细弹窗 */
handleNewCommand(item) {
if (typeof item === 'string') return
this.selectedNewClass = item
this.detailVisible = true
/** 本地年度过滤下拉(从当前数据动态生成,避免硬编码) */
buildYearFilterOptions() {
const years = this.tableData.map(r => r.nd).filter(v => v !== undefined && v !== null && v !== '')
this.yearFilterOptions = [...new Set(years)].sort((a, b) => Number(a) - Number(b))
},
handleDetailConfirm() {
this.$message.success('已创建班次学期信息(前端演示)')
handleYearChange() {
this.$nextTick(() => {
if (this.$refs.tableRef) this.$refs.tableRef.setCurrentRow(null)
this.currentRow = null
})
},
/** 编辑班历 */
handleEditCalendar() {
if (!this.currentRow) {
this.$message.warning('请先在列表中选择一条班次学期信息')
return
}
this.calendarVisible = true
handleSizeChange(size) {
this.pageSize = size
this.loadList()
},
/** 预设合班 */
handleMerge() {
this.teamVisible = true
handlePageChange(page) {
this.pageNum = page
this.loadList()
},
handleTeamConfirm(teams) {
this.$message.success(`预设合班已应用于 ${teams.length} 个班次(前端演示)`)
handleSelectionChange(rows) {
this.selection = rows
},
/** 配档编辑:选择班次后打开班次学期配档窗口 */
handlePeidangEdit() {
if (!this.currentRow) {
this.$message.warning('请先在列表中选择一个班次')
return
}
const row = this.currentRow
this.peidangClassLabel = `${row.xzdw}${row.nj}${row.xkzy}班次${row.bc}`
this.peidangVisible = true
},
handleSortCommand(command) {
this.sortType = command
},
/** 行样式:波次排序时同一波次使用同一背景色 */
rowClassName({ row }) {
if (this.sortType !== 'bw') return ''
const colorIndex = (row.bw - 1) % this.bwColors.length
return `bw-row bw-row-${colorIndex}`
},
// ==================== 4. 班次学期信息列表 ====================
handleCurrentChange(row) {
this.currentRow = row
},
/** 合并单元格:行政队别列(第一列)纵向合并相同值 */
spanMethod({ rowIndex, columnIndex }) {
if (columnIndex === 0) {
const list = this.displayData
const current = list[rowIndex]
// 非分组首行,隐藏
if (rowIndex > 0 && list[rowIndex - 1].xzdw === current.xzdw) {
return { rowspan: 0, colspan: 0 }
}
// 统计连续相同行政队别的行数
let rowspan = 1
for (let i = rowIndex + 1; i < list.length; i++) {
if (list[i].xzdw === current.xzdw) rowspan++
else break
}
return { rowspan, colspan: 1 }
}
return { rowspan: 1, colspan: 1 }
/* ==================== 查询 / 重置 ==================== */
handleQuery() {
this.pageNum = 1
this.loadList()
},
handleReset() {
this.searchForm.xydbh = ''
this.filterYear = undefined
this.pageNum = 1
this.loadList()
},
/** 表头特殊样式:总课程数 / 总课时数列红色框标注 */
headerCellClassName({ column }) {
if (column.property === 'kcrws' || column.property === 'kczs') {
return 'red-header-cell'
/* ==================== 新增 / 编辑 ==================== */
handleAdd() {
this.detailMode = 'add'
this.detailSemester = null
this.detailTeamInfo = null
this.detailVisible = true
},
handleEdit(row) {
const target = row || this.currentRow
if (!target) {
this.$message.warning('请先选择一条班次学期信息')
return
}
return ''
this.detailMode = 'edit'
this.detailSemester = target
this.detailTeamInfo = null
this.detailVisible = true
},
handleDetailSubmit(payload) {
this.saving = true
const req = this.detailMode === 'edit'
? updateSemester(payload)
: addSemester(payload)
req.then(() => {
this.saving = false
this.detailVisible = false
this.$message.success(this.detailMode === 'edit' ? '班次学期已更新' : '班次学期已新增')
this.loadList()
this.loadSemesterOptions()
}).catch(() => {
this.saving = false
})
},
/* ==================== 删除 ==================== */
handleDelete() {
if (!this.selection.length) {
this.$message.warning('请先勾选要删除的班次学期')
return
}
this.$confirm(`确定删除选中的 ${this.selection.length} 条班次学期吗?删除后其下班历记录将一并删除。`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
const bhList = this.selection.map(r => r.bh)
batchDeleteSemester(bhList).then(() => {
this.$message.success('已删除')
this.loadList()
this.loadSemesterOptions()
})
}).catch(() => {})
},
handleDeleteRow(row) {
this.$confirm(`确定删除班次学期「${row.xydbh || row.bh}」吗?删除后其下班历记录将一并删除。`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
delSemester(row.bh).then(() => {
this.$message.success('已删除')
this.loadList()
this.loadSemesterOptions()
})
}).catch(() => {})
},
/* ==================== 编辑班历 ==================== */
handleEditCalendar(row) {
const target = row || this.currentRow
if (!target) {
this.$message.warning('请先在列表中选择一条班次学期信息')
return
}
this.calendarSemester = target
this.calendarVisible = true
},
/* ==================== 批量创建班次学期 ==================== */
handleBatchAdd() {
this.batchAddForm = { nd: this.yearOptions[0] || undefined, xqdc: '', startTime: '', endTime: '' }
this.batchAddVisible = true
this.$nextTick(() => {
if (this.$refs.batchAddFormRef) this.$refs.batchAddFormRef.clearValidate()
})
},
handleBatchAddSubmit() {
this.$refs.batchAddFormRef.validate(valid => {
if (!valid) return
const f = this.batchAddForm
this.saving = true
batchAddFromElective({
nd: f.nd,
xqdc: f.xqdc,
startTime: f.startTime,
endTime: f.endTime
}).then(res => {
this.saving = false
this.batchAddVisible = false
const count = (res && res.data) || 0
this.$message.success(`已创建 ${count} 个班次学期`)
this.loadList()
this.loadSemesterOptions()
}).catch(() => {
this.saving = false
})
})
},
/* ==================== 快速设定学期日期 ==================== */
handleQuickSetDates() {
if (!this.selection.length) {
this.$message.warning('请先勾选要设定日期的班次学期')
return
}
this.dateRangeForm = { kxrq: '', jsrq: '' }
this.dateRangeVisible = true
this.$nextTick(() => {
if (this.$refs.dateRangeFormRef) this.$refs.dateRangeFormRef.clearValidate()
})
},
handleDateRangeSubmit() {
this.$refs.dateRangeFormRef.validate(valid => {
if (!valid) return
const f = this.dateRangeForm
this.saving = true
batchUpdateDateRange({
bhList: this.selection.map(r => r.bh),
xqkssj: f.kxrq,
xqjssj: f.jsrq
}).then(res => {
this.saving = false
this.dateRangeVisible = false
const count = (res && res.data) || 0
this.$message.success(`已更新 ${count} 条班次学期的日期`)
this.loadList()
}).catch(() => {
this.saving = false
})
})
},
/* ==================== 批量设置教学任务 ==================== */
handleSetJxrw() {
if (!this.selection.length) {
this.$message.warning('请先勾选要设置教学任务的班次学期')
return
}
this.jxrwForm = { jxrwbh: undefined }
this.jxrwVisible = true
this.$nextTick(() => {
if (this.$refs.jxrwFormRef) this.$refs.jxrwFormRef.clearValidate()
})
},
handleJxrwSubmit() {
this.$refs.jxrwFormRef.validate(valid => {
if (!valid) return
this.saving = true
batchUpdateJxrwbh({
bhList: this.selection.map(r => r.bh),
jxrwbh: this.jxrwForm.jxrwbh
}).then(res => {
this.saving = false
this.jxrwVisible = false
const count = (res && res.data) || 0
this.$message.success(`已更新 ${count} 条班次学期的教学任务`)
this.loadList()
}).catch(() => {
this.saving = false
})
})
}
}
}
@@ -375,8 +597,8 @@ export default {
}
.shift-semester-page {
// ========== 1. 学年学期标题 ==========
.list-title {
// ========== 页面标题 ==========
.page-title {
font-size: 16px;
font-weight: 600;
color: #303133;
@@ -385,44 +607,12 @@ export default {
border-left: 4px solid var(--edu-green-primary);
}
// ========== 2. 校历 / 预设日期信息区 ==========
// ========== 1. 查询条件区域 ==========
.search-card {
margin-bottom: 16px;
.search-form {
.date-group {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 4px;
margin-bottom: 12px;
&:last-child {
margin-bottom: 0;
}
.date-group-label {
flex-shrink: 0;
font-size: 14px;
font-weight: 600;
color: #303133;
margin-right: 8px;
}
.absorb-btn {
margin-left: auto;
align-self: center;
}
.el-form-item {
margin-bottom: 0;
margin-right: 16px;
}
}
}
}
// ========== 3. 工具栏按钮区 ==========
// ========== 2. 工具栏按钮区 ==========
.list-toolbar {
display: flex;
align-items: center;
@@ -440,46 +630,19 @@ export default {
}
}
// ========== 新建下拉菜单 ==========
.new-class-menu {
max-height: 320px;
overflow-y: auto;
}
// ========== 4. 数据表格区 ==========
$bwColors: (#e3f2fd, #e8f5e9, #fff8e1, #f3e5f5, #e0f2f1);
// ========== 3. 数据表格区 ==========
.table-card {
flex: 1;
.class-table {
width: 100%;
}
// 波次背景色(同一合班波次使用同一背景颜色连在一起显示)
@for $i from 0 through length($bwColors) - 1 {
::v-deep(.el-table__body tr.bw-row-#{$i} > td.el-table__cell) {
background-color: nth($bwColors, $i + 1);
}
.text-danger {
color: #f56c6c;
}
::v-deep(.el-table__body tr.bw-row-#{$i}:hover > td.el-table__cell) {
background-color: nth($bwColors, $i + 1);
}
}
// 当前行高亮(优先于波次背景色)
::v-deep(.el-table__body tr.current-row > td.el-table__cell) {
background-color: #e1f5ec;
}
::v-deep(.el-table__body tr.current-row:hover > td.el-table__cell) {
background-color: #e1f5ec;
}
// 表头红色框标注列(总课程数 / 总课时数)
::v-deep(th.red-header-cell) {
border: 2px solid #f56c6c !important;
background: #fdeaea !important;
}
.list-pagination {
margin-top: 12px;
text-align: right;
}
}
}
+314 -444
View File
@@ -1,7 +1,5 @@
<template>
<div class="app-container subject-page">
<!-- ==================== 1. 顶部状态提示 ==================== -->
<div class="status-tip">{{ statusTip }}</div>
<!-- ==================== 2. 查询条件区域 ==================== -->
<el-card shadow="never" class="search-card">
@@ -10,152 +8,41 @@
<!-- 左栏 -->
<el-col :xs="24" :md="12">
<div class="query-field">
<span class="q-label no-check">课程别名</span>
<el-input v-model="searchForm.kcbh" placeholder="请输入课程别名" clearable class="q-control" />
<span class="q-label no-check">课名称</span>
<el-input v-model="searchForm.kmc" placeholder="请输入课名称" clearable class="q-control" />
</div>
<div class="query-field">
<span class="q-label no-check">简称</span>
<el-input v-model="searchForm.jc" placeholder="请输入简称" clearable class="q-control" />
<span class="q-label no-check">科目代码</span>
<el-input v-model="searchForm.kmdm" placeholder="请输入科目代码" clearable class="q-control" />
</div>
<div class="query-field">
<span class="q-label no-check">学分</span>
<div class="q-control range-control">
<el-input v-model="searchForm.xfMin" placeholder="0" />
<span class="range-sep">至</span>
<el-input v-model="searchForm.xfMax" placeholder="0" />
</div>
<span class="q-label no-check">培训层次</span>
<el-input v-model="searchForm.pxcc" placeholder="请输入培训层次" clearable class="q-control" />
</div>
<div class="query-field">
<span class="q-label no-check">学时</span>
<div class="q-control range-control">
<el-input v-model="searchForm.xsMin" placeholder="0" />
<span class="range-sep">至</span>
<el-input v-model="searchForm.xsMax" placeholder="0" />
</div>
</div>
<div class="query-field">
<el-checkbox v-model="searchForm.zcfsEnabled" />
<span class="q-label">支持方式</span>
<el-select v-model="searchForm.zcfs" class="q-control" :disabled="!searchForm.zcfsEnabled">
<el-option v-for="item in zcfsOptions" :key="item" :label="item" :value="item" />
</el-select>
</div>
<div class="query-field">
<el-checkbox v-model="searchForm.khlxEnabled" />
<span class="q-label">考核类型</span>
<el-select v-model="searchForm.khlx" class="q-control" :disabled="!searchForm.khlxEnabled">
<el-option v-for="item in khlxOptions" :key="item" :label="item" :value="item" />
</el-select>
</div>
<div class="query-field">
<span class="q-label no-check">秘密等级</span>
<el-select v-model="searchForm.mmdj" placeholder="请选择" class="q-control">
<el-option v-for="item in mmdjOptions" :key="item" :label="item" :value="item" />
</el-select>
</div>
<div class="query-field">
<el-checkbox v-model="searchForm.dgxzEnabled" />
<span class="q-label">大纲性质</span>
<el-radio-group v-model="searchForm.dgxz" class="q-control" :disabled="!searchForm.dgxzEnabled">
<el-radio :label="'大纲课程'">大纲课程</el-radio>
<el-radio :label="'非大纲课程'">非大纲课程</el-radio>
</el-radio-group>
</div>
<div class="query-field">
<el-checkbox v-model="searchForm.zzglEnabled" />
<span class="q-label">政治类课程</span>
<el-radio-group v-model="searchForm.zzgl" class="q-control" :disabled="!searchForm.zzglEnabled">
<el-radio :label="'政治类'">政治类</el-radio>
<el-radio :label="'非政治类'">非政治类</el-radio>
</el-radio-group>
</div>
<div class="query-field">
<span class="q-label no-check">备注</span>
<el-input v-model="searchForm.bz" placeholder="请输入备注" clearable class="q-control" />
<span class="q-label no-check">课程类型</span>
<el-input v-model="searchForm.kclx" placeholder="请输入课程类型" clearable class="q-control" />
</div>
</el-col>
<!-- 右栏 -->
<el-col :xs="24" :md="12">
<div class="query-field">
<el-checkbox v-model="searchForm.jysEnabled" />
<span class="q-label">教研室</span>
<el-select v-model="searchForm.jys" class="q-control" :disabled="!searchForm.jysEnabled">
<el-option v-for="item in jysOptions" :key="item" :label="item" :value="item" />
</el-select>
<span class="q-label no-check">简称</span>
<el-input v-model="searchForm.jc" placeholder="请输入简称" clearable class="q-control" />
</div>
<div class="query-field">
<el-checkbox v-model="searchForm.pxccEnabled" />
<span class="q-label">培训层次</span>
<el-select v-model="searchForm.pxcc" class="q-control" :disabled="!searchForm.pxccEnabled">
<el-option v-for="item in pxccOptions" :key="item" :label="item" :value="item" />
</el-select>
<span class="q-label no-check">教研室代号</span>
<el-input v-model="searchForm.jysdh" placeholder="请输入教研室代号" clearable class="q-control" />
</div>
<div class="query-field">
<el-checkbox v-model="searchForm.pxlxEnabled" />
<span class="q-label">培训类型</span>
<el-select v-model="searchForm.pxlx" class="q-control" :disabled="!searchForm.pxlxEnabled">
<el-option v-for="item in pxlxOptions" :key="item" :label="item" :value="item" />
</el-select>
</div>
<div class="query-field">
<el-checkbox v-model="searchForm.kclxEnabled" />
<span class="q-label">课程类型</span>
<el-select v-model="searchForm.kclx" class="q-control" :disabled="!searchForm.kclxEnabled">
<el-option v-for="item in kclxOptions" :key="item" :label="item" :value="item" />
</el-select>
</div>
<div class="query-field">
<el-checkbox v-model="searchForm.jsfsEnabled" />
<span class="q-label">建设方式</span>
<el-select v-model="searchForm.jsfs" class="q-control" :disabled="!searchForm.jsfsEnabled">
<el-option v-for="item in jsfsOptions" :key="item" :label="item" :value="item" />
</el-select>
</div>
<div class="query-field">
<el-checkbox v-model="searchForm.pjqkEnabled" />
<span class="q-label">评选情况</span>
<el-select v-model="searchForm.pjqk" class="q-control" :disabled="!searchForm.pjqkEnabled">
<el-option v-for="item in pjqkOptions" :key="item" :label="item" :value="item" />
</el-select>
</div>
<div class="query-field">
<el-checkbox v-model="searchForm.gljgEnabled" />
<span class="q-label">管理机构</span>
<el-select v-model="searchForm.gljg" class="q-control" :disabled="!searchForm.gljgEnabled">
<el-option v-for="item in gljgOptions" :key="item" :label="item" :value="item" />
</el-select>
</div>
<div class="query-field">
<el-checkbox v-model="searchForm.zyxzEnabled" />
<span class="q-label">在用选项</span>
<el-radio-group v-model="searchForm.zyxz" class="q-control" :disabled="!searchForm.zyxzEnabled">
<el-radio :label="'在用'">在用</el-radio>
<el-radio :label="'其他'">其他</el-radio>
</el-radio-group>
</div>
<div class="query-field">
<el-checkbox v-model="searchForm.bbxxEnabled" />
<span class="q-label">版本选项</span>
<el-radio-group v-model="searchForm.bbxx" class="q-control" :disabled="!searchForm.bbxxEnabled">
<el-radio :label="'最新版本'">最新版本</el-radio>
<el-radio :label="'存在多版本'">存在多版本</el-radio>
<el-radio :label="'全部版本'">全部版本</el-radio>
</el-radio-group>
</div>
<div class="query-field">
<el-checkbox v-model="searchForm.bmbhEnabled" />
<span class="q-label">别名包含规范名称</span>
<el-radio-group v-model="searchForm.bmbh" class="q-control" :disabled="!searchForm.bmbhEnabled">
<el-radio :label="'完全包含'">完全包含</el-radio>
<el-radio :label="'未完全包含'">未完全包含</el-radio>
</el-radio-group>
<span class="q-label no-check">培训类型</span>
<el-input v-model="searchForm.pxlx" placeholder="请输入培训类型" clearable class="q-control" />
</div>
</el-col>
</el-row>
</el-form>
<div class="notice">注意:勾选"选择框"表示启用该项对应的查询条件,输入框非空表示启用查询条件。</div>
<div class="query-footer">
<el-button class="gray-btn" @click="handleQuery">查询</el-button>
</div>
@@ -189,27 +76,26 @@
</div>
</el-card>
<!-- ==================== 5. 红色提示文字 ==================== -->
<div class="red-tip">
提示:课程体系按"课程-专题-课题"构建的,课程名称的命名格式为"课程名称\专题名称\课题名称"或"课程名称\课题名称"。
</div>
<!-- ==================== 6. 数据表格区域 ==================== -->
<el-card shadow="never" class="table-card">
<el-table v-loading="loading" :data="tableData" stripe border highlight-current-row
@selection-change="handleSelectionChange">
<el-table-column type="selection" width="40" align="center" />
<el-table-column prop="manage" label="管理" width="70" align="center" />
<el-table-column prop="code" label="编码" width="100" show-overflow-tooltip />
<el-table-column prop="name" label="课程名称" min-width="130" show-overflow-tooltip />
<el-table-column prop="cat" label="分类" width="100" align="center" />
<el-table-column prop="exam" label="考核类型方式" width="110" align="center" />
<el-table-column prop="inClass" label="课内学时" width="90" align="center" />
<el-table-column prop="outClass" label="课外学时" width="90" align="center" />
<el-table-column prop="other" label="其他" width="80" align="center" />
<el-table-column prop="textbook" label="教材" min-width="120" show-overflow-tooltip />
<el-table-column prop="majors" label="开设课程专业名称" min-width="150" show-overflow-tooltip />
<el-table-column prop="teacher" label="在职主讲教员" min-width="110" show-overflow-tooltip />
<el-table-column prop="kbh" label="课编号" width="130" show-overflow-tooltip />
<el-table-column prop="kmc" label="课名称" width="200" show-overflow-tooltip />
<el-table-column prop="jc" label="简称" width="130" show-overflow-tooltip />
<el-table-column prop="jysdh" label="教研室代号" width="150" show-overflow-tooltip />
<el-table-column prop="kclx" label="课程类型" width="150" align="center" />
<el-table-column prop="pxlx" label="培训类型" width="150" align="center" />
<el-table-column prop="pxcc" label="培训层次" width="200" align="center" />
<el-table-column prop="xf" label="学分" align="center" />
<el-table-column prop="xs" label="学时" width="160" align="center" />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template slot-scope="{ row }">
<el-button type="text" size="small" @click="handleEdit(row)">编辑</el-button>
<el-button type="text" size="small" class="text-danger" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
@@ -228,119 +114,178 @@
<el-dialog
:visible="dialogVisible"
:title="dialogTitle"
width="1100px"
width="1000px"
:close-on-click-modal="false"
class="add-dialog"
@update:visible="val => dialogVisible = val"
>
<el-form ref="addFormRef" :model="addForm" :rules="rules" label-width="120px" class="add-form" :status-icon="false">
<el-row :gutter="24">
<el-col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
<el-form-item label="编号" prop="bh">
<el-input v-model="addForm.bh" placeholder="请输入编号" clearable />
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="课编号" prop="kbh">
<el-input v-model="addForm.kbh" :disabled="isEdit" placeholder="新增时留空自动生成" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
<el-form-item label="课标号" prop="kbh">
<el-input v-model="addForm.kbh" placeholder="请输入课标号" clearable />
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="课名称" prop="kmc">
<el-input v-model="addForm.kmc" placeholder="请输入课名称" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
<el-form-item label="课程名称" prop="mc">
<el-input v-model="addForm.mc" placeholder="请输入课程名称" clearable />
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="简称" prop="jc">
<el-input v-model="addForm.jc" placeholder="请输入简称" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
<el-form-item label="适用专业" prop="syzy">
<el-input v-model="addForm.syzy" placeholder="请输入适用专业" clearable />
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="教研室代号" prop="jysdh">
<el-input v-model="addForm.jysdh" placeholder="请输入教研室代号" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
<el-form-item label="课时数" prop="kcs">
<el-input-number v-model="addForm.kcs" :min="0" controls-position="right" class="w-full" />
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="学分" prop="xf">
<el-input-number v-model="addForm.xf" :min="0" :precision="1" controls-position="right" class="w-full" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
<el-form-item label="录入人编号" prop="zxrbh">
<el-input v-model="addForm.zxrbh" placeholder="请输入录入人编号" clearable />
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="学时" prop="xs">
<el-input-number v-model="addForm.xs" :min="0" controls-position="right" class="w-full" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
<el-form-item label="录入时间">
<el-input v-model="addForm.zxsj" placeholder="请输入录入时间" clearable />
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="培训层次" prop="pxcc">
<el-input v-model="addForm.pxcc" placeholder="请输入培训层次" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<el-form-item label="正文">
<el-input v-model="addForm.zw" type="textarea" :rows="3" placeholder="请输入正文" />
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="培训类型" prop="pxlx">
<el-input v-model="addForm.pxlx" placeholder="请输入培训类型" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="课程类型" prop="kclx">
<el-input v-model="addForm.kclx" placeholder="请输入课程类型" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="考试课时" prop="ksks">
<el-input-number v-model="addForm.ksks" :min="0" controls-position="right" class="w-full" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="成绩分制" prop="cjfz">
<el-input v-model="addForm.cjfz" placeholder="请输入成绩分制" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="不计入学员平均分" prop="bjrxypjf">
<el-select v-model="addForm.bjrxypjf" class="w-full">
<el-option label="否" :value="0" />
<el-option label="是" :value="1" />
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="理论学时" prop="llxs">
<el-input-number v-model="addForm.llxs" :min="0" controls-position="right" class="w-full" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="实践学时" prop="sjxs">
<el-input-number v-model="addForm.sjxs" :min="0" controls-position="right" class="w-full" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="周课时" prop="zks">
<el-input-number v-model="addForm.zks" :min="0" controls-position="right" class="w-full" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="课类型" prop="klx">
<el-input v-model="addForm.klx" placeholder="请输入课类型" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="考试课时不显示" prop="ksksbxs">
<el-select v-model="addForm.ksksbxs" class="w-full">
<el-option label="否" :value="0" />
<el-option label="是" :value="1" />
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="课程统一编号" prop="kctybh">
<el-input v-model="addForm.kctybh" placeholder="请输入课程统一编号" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="辅导答疑课时" prop="fddyks">
<el-input-number v-model="addForm.fddyks" :min="0" controls-position="right" class="w-full" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="自修课时" prop="zxks">
<el-input-number v-model="addForm.zxks" :min="0" controls-position="right" class="w-full" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="修订日期" prop="xdrq">
<el-input v-model="addForm.xdrq" placeholder="如 2026-08-24" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="节次优选级" prop="jcyxj">
<el-input v-model="addForm.jcyxj" placeholder="请输入节次优选级" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="课程支持方式" prop="kczcfs">
<el-input v-model="addForm.kczcfs" placeholder="请输入课程支持方式" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="课程建设方式" prop="kcjsfs">
<el-input v-model="addForm.kcjsfs" placeholder="请输入课程建设方式" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="政治类课程" prop="zzlkc">
<el-select v-model="addForm.zzlkc" class="w-full">
<el-option label="否" :value="0" />
<el-option label="是" :value="1" />
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="考核方式" prop="khfs">
<el-input v-model="addForm.khfs" placeholder="请输入考核方式" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="形成性成绩分值" prop="xcxcjfz">
<el-input-number v-model="addForm.xcxcjfz" :min="0" controls-position="right" class="w-full" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="终结性成绩分值" prop="zjxcjfz">
<el-input-number v-model="addForm.zjxcjfz" :min="0" controls-position="right" class="w-full" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="密级" prop="mj">
<el-input v-model="addForm.mj" placeholder="请输入密级" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<el-form-item label="评选情况" prop="pxqk">
<el-input v-model="addForm.pxqk" placeholder="请输入评选情况" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24">
<el-form-item label="备注">
<el-input v-model="addForm.bz" type="textarea" :rows="2" placeholder="请输入备注" />
</el-form-item>
</el-col>
</el-row>
<div class="classroom-section">
<div class="classroom-header">
<span class="classroom-title">课堂列表</span>
<el-button type="primary" size="small" @click="handleAddClassroom">添加课堂</el-button>
</div>
<el-table :data="addForm.ktList" border size="small">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column label="课堂编号" width="140">
<template slot-scope="{ row }">
<el-input v-model="row.bh" placeholder="课堂编号" />
</template>
</el-table-column>
<el-table-column label="课堂次序" width="90">
<template slot-scope="{ row }">
<el-input-number v-model="row.ktjc" :min="1" controls-position="right" class="w-full" />
</template>
</el-table-column>
<el-table-column label="教学内容" min-width="150">
<template slot-scope="{ row }">
<el-input v-model="row.jxnr" placeholder="教学内容" />
</template>
</el-table-column>
<el-table-column label="教学要点" min-width="150">
<template slot-scope="{ row }">
<el-input v-model="row.jxyd" placeholder="教学要点" />
</template>
</el-table-column>
<el-table-column label="教学方法" min-width="150">
<template slot-scope="{ row }">
<el-input v-model="row.jxff" placeholder="教学方法" />
</template>
</el-table-column>
<el-table-column label="主讲教员数" width="100">
<template slot-scope="{ row }">
<el-input-number v-model="row.zjjys" :min="0" controls-position="right" class="w-full" />
</template>
</el-table-column>
<el-table-column label="辅讲教员数" width="100">
<template slot-scope="{ row }">
<el-input-number v-model="row.fjjys" :min="0" controls-position="right" class="w-full" />
</template>
</el-table-column>
<el-table-column label="教学目的" min-width="150">
<template slot-scope="{ row }">
<el-input v-model="row.jxmd" placeholder="教学目的" />
</template>
</el-table-column>
<el-table-column label="教学要求" min-width="150">
<template slot-scope="{ row }">
<el-input v-model="row.jxyq" placeholder="教学要求" />
</template>
</el-table-column>
<el-table-column label="操作" width="80" align="center" fixed="right">
<template slot-scope="{ $index }">
<el-button type="text" class="text-danger" @click="handleDeleteClassroom($index)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-form>
<div slot="footer">
<div class="dialog-footer">
@@ -349,34 +294,12 @@
</div>
</div>
</el-dialog>
<!-- 撤回弹窗 -->
<el-dialog
:visible="withdrawDialogVisible"
title="撤回课程科目"
width="500px"
:close-on-click-modal="false"
@update:visible="val => withdrawDialogVisible = val"
>
<el-form :model="withdrawForm" label-width="100px">
<el-form-item label="课程编号">
<el-input v-model="withdrawForm.bh" disabled />
</el-form-item>
<el-form-item label="撤回原因">
<el-input v-model="withdrawForm.chyy" type="textarea" :rows="4" placeholder="请输入撤回原因" />
</el-form-item>
</el-form>
<div slot="footer">
<div class="dialog-footer">
<el-button @click="handleCancelWithdraw">取消</el-button>
<el-button type="primary" :loading="withdrawLoading" @click="handleWithdraw">确定撤回</el-button>
</div>
</div>
</el-dialog>
</div>
</template>
<script>
import { listSubject, addSubject, updateSubject, deleteSubject, getSubject } from '@/api/teachOffice/subject'
export default {
name: 'SubjectIndex',
data() {
@@ -384,68 +307,18 @@ export default {
// ==================== 1. 顶部状态提示 ====================
statusTip: '请输入查询条件',
// ==================== 下拉选项 ====================
zcfsOptions: ['其他', '讲授', '研讨', '实操'],
khlxOptions: ['讲座', '考试', '考查', '实操'],
mmdjOptions: ['公开', '内部', '秘密', '机密'],
jysOptions: ['未确定教研室', '军事基础教研室', '政治工作教研室', '装备保障教研室'],
pxccOptions: ['无', '本科', '硕士', '博士'],
pxlxOptions: ['军官基础教育', '士兵职业教育', '研究生教育', '其他'],
kclxOptions: ['其他类', '公共基础', '专业基础', '专业核心'],
jsfsOptions: ['其他', '自建', '共建', '引进'],
pjqkOptions: ['无', '国家级', '军队级', '院校级'],
gljgOptions: ['教务处', '教研室1', '教研室2'],
// ==================== 2. 查询条件 ====================
searchForm: {
kcbh: '',
kmc: '',
jc: '',
xfMin: '',
xfMax: '',
xsMin: '',
xsMax: '',
zcfsEnabled: false,
zcfs: '其他',
khlxEnabled: false,
khlx: '讲座',
mmdj: '',
dgxzEnabled: false,
dgxz: '大纲课程',
zzglEnabled: false,
zzgl: '政治类',
bz: '',
jysEnabled: false,
jys: '未确定教研室',
pxccEnabled: false,
pxcc: '无',
pxlxEnabled: false,
pxlx: '军官基础教育',
kclxEnabled: false,
kclx: '其他类',
jsfsEnabled: false,
jsfs: '其他',
pjqkEnabled: false,
pjqk: '无',
gljgEnabled: false,
gljg: '教务处',
zyxzEnabled: true,
zyxz: '在用',
bbxxEnabled: true,
bbxx: '最新版本',
bmbhEnabled: false,
bmbh: '完全包含'
kmdm: '',
jysdh: '',
kclx: '',
pxlx: '',
pxcc: ''
},
// ==================== 列表数据 ====================
/** 模拟列表数据(后端接口未提供) */
mockTableData: [
{ id: 1, manage: '编辑', code: 'KCB001', name: '高等数学', cat: '公共基础', exam: '考试', inClass: 48, outClass: 16, other: '—', textbook: '高等数学(上册)', majors: '炮兵装备运用/通信工程', teacher: '张三' },
{ id: 2, manage: '编辑', code: 'KCB002', name: '导弹发射原理', cat: '专业核心', exam: '考试', inClass: 40, outClass: 16, other: '—', textbook: '导弹发射原理', majors: '炮兵装备运用', teacher: '李四' },
{ id: 3, manage: '编辑', code: 'KCB003', name: '通信原理', cat: '专业核心', exam: '考试', inClass: 48, outClass: 16, other: '—', textbook: '通信原理', majors: '通信工程', teacher: '王五' },
{ id: 4, manage: '编辑', code: 'KCB004', name: '军事英语', cat: '公共基础', exam: '考查', inClass: 30, outClass: 8, other: '—', textbook: '军事英语教程', majors: '全军各专业', teacher: '赵六' },
{ id: 5, manage: '编辑', code: 'KCB005', name: '联合作战指挥', cat: '专业核心', exam: '综合考核', inClass: 40, outClass: 20, other: '—', textbook: '联合作战指挥', majors: '军事指挥', teacher: '孙七' },
{ id: 6, manage: '编辑', code: 'KCB006', name: '装备维修训练', cat: '实践环节', exam: '实操', inClass: 0, outClass: 48, other: '—', textbook: '装备维修手册', majors: '装备保障', teacher: '周八' }
],
loading: false,
tableData: [],
total: 0,
@@ -460,22 +333,34 @@ export default {
// ==================== 新增/编辑弹窗 ====================
dialogVisible: false,
addLoading: false,
editLoading: false,
isEdit: false,
addForm: this.createEmptyForm(),
rules: {
bh: [{ required: true, message: '请输入编号', trigger: 'blur' }],
kbh: [{ required: true, message: '请输入课标号', trigger: 'blur' }],
mc: [{ required: true, message: '请输入课程名称', trigger: 'blur' }],
syzy: [{ required: true, message: '请输入适用专业', trigger: 'blur' }],
kcs: [{ required: true, message: '请输入课时数', trigger: 'blur' }],
zxrbh: [{ required: true, message: '请输入录入人编号', trigger: 'blur' }]
},
// ==================== 撤回弹窗 ====================
withdrawDialogVisible: false,
withdrawLoading: false,
withdrawForm: { bh: '', chyy: '' }
kmc: [{ required: true, message: '请输入课名称', trigger: 'blur' }],
jysdh: [{ required: true, message: '请输入教研室代号', trigger: 'blur' }],
xf: [{ required: true, message: '请输入学分', trigger: 'blur' }],
pxcc: [{ required: true, message: '请输入培训层次', trigger: 'blur' }],
pxlx: [{ required: true, message: '请输入培训类型', trigger: 'blur' }],
kclx: [{ required: true, message: '请输入课程类型', trigger: 'blur' }],
ksks: [{ required: true, message: '请输入考试课时', trigger: 'blur' }],
cjfz: [{ required: true, message: '请输入成绩分制', trigger: 'blur' }],
llxs: [{ required: true, message: '请输入理论学时', trigger: 'blur' }],
sjxs: [{ required: true, message: '请输入实践学时', trigger: 'blur' }],
zks: [{ required: true, message: '请输入周课时', trigger: 'blur' }],
klx: [{ required: true, message: '请输入课类型', trigger: 'blur' }],
kctybh: [{ required: true, message: '请输入课程统一编号', trigger: 'blur' }],
fddyks: [{ required: true, message: '请输入辅导答疑课时', trigger: 'blur' }],
zxks: [{ required: true, message: '请输入自修课时', trigger: 'blur' }],
xdrq: [{ required: true, message: '请输入修订日期', trigger: 'blur' }],
jcyxj: [{ required: true, message: '请输入节次优选级', trigger: 'blur' }],
kczcfs: [{ required: true, message: '请输入课程支持方式', trigger: 'blur' }],
kcjsfs: [{ required: true, message: '请输入课程建设方式', trigger: 'blur' }],
khfs: [{ required: true, message: '请输入考核方式', trigger: 'blur' }],
xcxcjfz: [{ required: true, message: '请输入形成性成绩分值', trigger: 'blur' }],
zjxcjfz: [{ required: true, message: '请输入终结性成绩分值', trigger: 'blur' }],
mj: [{ required: true, message: '请输入密级', trigger: 'blur' }],
pxqk: [{ required: true, message: '请输入评选情况', trigger: 'blur' }]
}
}
},
computed: {
@@ -486,54 +371,69 @@ export default {
return (this.selectedFile && this.selectedFile.name) || '未选择任何文件'
}
},
created() {
// 初始化表单(保证 getCurrentTime 可用)
this.addForm = this.createEmptyForm()
},
mounted() {
this.fetchList()
},
methods: {
// ==================== 表单辅助 ====================
getCurrentTime() {
return new Date().toLocaleString('zh-CN', { hour12: false }).replace(/\//g, '-')
/** 移除空值 */
cleanPayload(obj) {
const payload = {}
Object.keys(obj).forEach(key => {
const value = obj[key]
if (value !== '' && value !== null && value !== undefined) {
payload[key] = value
}
})
return payload
},
/** 创建空白课程表单(含一条空课堂记录) */
/** 创建空白课程科目表单 */
createEmptyForm() {
return {
bh: '', kbh: '', mc: '', syzy: '', kcs: 0, bz: '', zxrbh: '', zxsj: this.getCurrentTime(),
zxzt: '0', sbzt: '0', sbsj: '', sbrbh: '', chyy: '', zw: '', jsonzd: '', ktList: [this.defaultClassroom()]
kbh: '', kmc: '', jysdh: '', bz: '', xh: '', xf: 0, jc: '', xs: 0,
pxcc: '', pxlx: '', kclx: '', ksks: 0, py: '', gfmc: '', cjfz: '',
bjrxypjf: 0, jxglbmbh: '', llxs: 0, sjxs: 0, kmdm: '', zks: 0, klx: '',
ksksbxs: 0, kctybh: '', sydx: '', fddyks: 0, zxks: 0, bb: '', xdrq: '',
jcyxj: '', kczcfs: '', kcjsfs: '', jsonzd: '', zzlkc: 0, khfs: '',
xcxcjfz: 0, zjxcjfz: 0, mj: '', pxqk: ''
}
},
/** 默认课堂项 */
defaultClassroom() {
return { bh: '', ktjc: 1, jxnr: '', jxyd: '', jxff: '', zjjys: 0, fjjys: 0, bz: '', jxmd: '', jxyq: '' }
},
resetForm() {
this.addForm = this.createEmptyForm()
},
// ==================== 列表加载 ====================
// TODO: 后端接口未提供,暂用模拟数据;接口就绪后替换为 getCourseTeachingList
buildQuery() {
return this.cleanPayload({
pageNum: this.pageNum,
pageSize: this.pageSize,
kmc: this.searchForm.kmc,
jc: this.searchForm.jc,
kmdm: this.searchForm.kmdm,
jysdh: this.searchForm.jysdh,
kclx: this.searchForm.kclx,
pxlx: this.searchForm.pxlx,
pxcc: this.searchForm.pxcc
})
},
fetchList() {
this.loading = true
setTimeout(() => {
this.tableData = [...this.mockTableData]
this.total = this.mockTableData.length
this.loading = false
}, 200)
listSubject(this.buildQuery())
.then(res => {
const data = (res && res.data) || {}
this.tableData = data.records || []
this.total = data.total || 0
this.statusTip = `共检索到 ${this.total} 条记录`
})
.finally(() => {
this.loading = false
})
},
// ==================== 查询 ====================
handleQuery() {
const now = new Date()
const hh = String(now.getHours()).padStart(2, '0')
const mm = String(now.getMinutes()).padStart(2, '0')
const ss = String(now.getSeconds()).padStart(2, '0')
this.statusTip = `[${hh}:${mm}:${ss}]查询成功!共检索到${this.total || 982}条记录。`
this.pageNum = 1
this.fetchList()
},
@@ -557,36 +457,54 @@ export default {
this.$message.warning('请先选择要删除的记录')
return
}
const ids = this.selectedRows.map((r) => r.id)
this.tableData = this.tableData.filter((r) => ids.indexOf(r.id) === -1)
this.total = this.tableData.length
this.$message.success(`已删除所选 ${ids.length} 条记录(前端模拟)`)
const kbhList = this.selectedRows.map(r => r.kbh)
this.$confirm(`确定删除所选 ${kbhList.length} 条课程科目吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
const tasks = kbhList.map(kbh => deleteSubject(kbh))
Promise.all(tasks)
.then(() => {
this.$message.success(`已删除 ${kbhList.length} 条记录`)
this.fetchList()
})
.catch(() => {
this.$message.error('删除失败')
})
}).catch(() => {})
},
// ==================== 下载 ====================
downloadBlob(content, filename) {
const blob = new Blob(['\ufeff' + content], { type: 'text/plain;charset=utf-8;' })
const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = filename
link.click()
URL.revokeObjectURL(link.href)
/** 单条删除 */
handleDelete(row) {
this.$confirm(`确定删除课程科目「${row.kmc || row.kbh}」吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
return deleteSubject(row.kbh)
}).then(() => {
this.$message.success('删除成功')
this.fetchList()
}).catch((err) => {
if (err && err !== 'cancel') {
this.$message.error('删除失败')
}
})
},
// ==================== 下载(后端暂未提供) ====================
handleDownloadCourse() {
this.downloadBlob('编码,课程名称,分类,考核类型方式,课内学时,课外学时\n', '课程科目基本信息.txt')
this.$message.success('课程科目基本信息已下载(前端模拟)')
this.$message.warning('后端暂未提供该接口')
},
handleDownloadTextbook() {
this.downloadBlob('课程名称,教材,在职主讲教员\n', '课程教材基本信息.txt')
this.$message.success('课程教材基本信息已下载(前端模拟)')
this.$message.warning('后端暂未提供该接口')
},
handleDownloadTemplate() {
this.downloadBlob('编码,课程名称,分类,考核类型方式,课内学时,课外学时,教材,开设课程专业名称,在职主讲教员\n', '课程科目数据文件模板.txt')
this.$message.success('【课程科目数据文件模板】已下载(前端模拟)')
this.$message.warning('后端暂未提供该接口')
},
// ==================== 文件上传 ====================
// ==================== 文件上传(后端暂未提供) ====================
handleSelectFile() {
this.$refs.fileInputRef && this.$refs.fileInputRef.click()
},
@@ -597,11 +515,7 @@ export default {
},
handleUpload() {
if (!this.selectedFile) {
this.$message.warning('请先选择文件')
return
}
this.$message.success(`文件「${this.selectedFile.name}」上传成功(前端模拟)`)
this.$message.warning('后端暂未提供该接口')
},
// ==================== 新增/编辑弹窗 ====================
@@ -611,49 +525,40 @@ export default {
this.dialogVisible = true
},
handleAddClassroom() {
this.addForm.ktList.push(this.defaultClassroom())
},
handleDeleteClassroom(index) {
this.addForm.ktList.splice(index, 1)
handleEdit(row) {
this.addLoading = true
getKb(row.kbh)
.then(res => {
const data = (res && res.data) || row
this.isEdit = true
this.addForm = Object.assign(this.createEmptyForm(), data)
this.dialogVisible = true
})
.finally(() => {
this.addLoading = false
})
},
// TODO: 后端接口未提供,保存成功后刷新列表为前端模拟;接口就绪后替换为 addCourseTeaching/updateCourseTeaching
handleSubmit() {
this.$refs.addFormRef.validate((valid) => {
if (!valid) return
this.addLoading = true
setTimeout(() => {
this.$message.success(this.isEdit ? '修改成功' : '保存成功')
this.dialogVisible = false
this.fetchList()
this.addLoading = false
}, 300)
const payload = this.cleanPayload({ ...this.addForm })
const requestFn = this.isEdit ? updateSubject : addSubject
requestFn(payload)
.then(() => {
this.$message.success(this.isEdit ? '修改成功' : '保存成功')
this.dialogVisible = false
this.fetchList()
})
.finally(() => {
this.addLoading = false
})
})
},
handleCancel() {
this.dialogVisible = false
},
// ==================== 撤回弹窗 ====================
// TODO: 后端接口未提供,撤回为前端模拟;接口就绪后替换为 deleteCourseTeaching
handleWithdraw() {
if (!this.withdrawForm.chyy.trim()) {
this.$message.warning('请输入撤回原因')
return
}
this.withdrawLoading = true
setTimeout(() => {
this.$message.success('撤回成功')
this.withdrawDialogVisible = false
this.fetchList()
this.withdrawLoading = false
}, 300)
},
handleCancelWithdraw() {
this.withdrawDialogVisible = false
}
}
}
@@ -708,22 +613,6 @@ export default {
flex: 1;
min-width: 0;
}
.range-control {
display: flex;
align-items: center;
gap: 6px;
.el-input {
flex: 1;
}
.range-sep {
flex-shrink: 0;
font-size: 12px;
color: #606266;
}
}
}
.notice {
@@ -811,6 +700,10 @@ export default {
::v-deep(.el-table .cell) {
font-size: 12px;
}
.text-danger {
color: #f56c6c;
}
}
// ==================== 新增/编辑弹窗 ====================
@@ -825,29 +718,6 @@ export default {
.w-full {
width: 100%;
}
.classroom-section {
margin-top: 16px;
.classroom-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
.classroom-title {
font-size: 15px;
font-weight: 600;
color: #303133;
}
}
}
// 课堂列表删除按钮红色文字
.text-danger {
color: #f56c6c;
padding: 0;
}
}
.dialog-footer {
@@ -872,4 +742,4 @@ export default {
color: #333;
}
}
</style>
</style>
+397 -371
View File
@@ -1,376 +1,440 @@
<template>
<div class="app-container task-plan-page">
<!-- ==================== 1. 学期信息区域 ==================== -->
<!-- ==================== 页面标题 ==================== -->
<div class="page-title">教学任务计划管理</div>
<!-- ==================== 1. 查询条件区域 ==================== -->
<el-card shadow="never" class="search-card">
<el-form :inline="true" :model="semester" class="search-form">
<el-form-item label="学期名称">
<el-input v-model="semester.name" style="width: 160px" />
</el-form-item>
<el-form-item label="校历开学日期">
<el-date-picker
v-model="semester.kxrq"
type="date"
format="yyyy年M月d日"
value-format="yyyy-MM-dd"
placeholder="选择日期"
style="width: 160px"
<el-form :inline="true" :model="searchForm" class="search-form">
<el-form-item label="任务名称">
<el-input
v-model="searchForm.rwmc"
placeholder="请输入任务名称"
clearable
style="width: 180px"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="结束日期">
<el-date-picker
v-model="semester.jsrq"
type="date"
format="yyyy年M月d日"
value-format="yyyy-MM-dd"
placeholder="选择日期"
style="width: 160px"
/>
<el-form-item label="年度">
<el-select v-model="searchForm.nd" placeholder="请选择年度" clearable style="width: 140px">
<el-option v-for="y in yearOptions" :key="y" :label="y" :value="y" />
</el-select>
</el-form-item>
<el-form-item label="学期周数">
<el-input v-model="semester.weeks" style="width: 90px" />
<el-form-item label="状态">
<el-select v-model="searchForm.zt" placeholder="请选择状态" clearable style="width: 140px">
<el-option
v-for="opt in statusOptions"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- ==================== 2. 数据表格区域 ==================== -->
<div class="list-title">教学任务列表:</div>
<!-- ==================== 2. 操作区域 ==================== -->
<div class="list-toolbar">
<div class="left-group">
<el-button type="primary" @click="handleAdd">新建</el-button>
<el-button type="primary" plain @click="handleEdit">编辑</el-button>
<el-button type="danger" plain @click="handleDelete">删除</el-button>
<el-button type="primary" plain @click="handlePublish">发布教学任务</el-button>
<el-button type="primary" plain @click="handleEndPublish">结束发布</el-button>
<el-button type="primary" plain @click="handleManageTeams">管理教学任务所含学员队</el-button>
<el-button type="primary" icon="el-icon-plus" @click="handleAdd">新建教学任务</el-button>
</div>
</div>
<!-- ==================== 3. 数据表格 ==================== -->
<el-card shadow="never" class="table-card">
<el-table
ref="tableRef"
v-loading="loading"
:data="tableData"
border
stripe
highlight-current-row
class="task-table"
@selection-change="handleSelectionChange"
@current-change="handleCurrentChange"
>
<el-table-column type="selection" width="40" align="center" />
<el-table-column prop="rwmc" label="任务名称" min-width="170" show-overflow-tooltip />
<el-table-column label="状态" width="80" align="center">
<el-table v-loading="loading" :data="tableData" border stripe class="task-table">
<template slot="empty">
<span>无数据!</span>
</template>
<el-table-column type="index" label="序号" width="70" align="center" />
<el-table-column prop="rwmc" label="任务名称" min-width="180" align="left" header-align="center" show-overflow-tooltip />
<el-table-column prop="nd" label="年度" width="90" align="center" />
<el-table-column label="状态" width="100" align="center">
<template slot-scope="{ row }">
<span class="status-text">{{ row.zt }}</span>
<el-tag :type="row.zt === '发布' ? 'success' : 'info'" size="small">
{{ row.zt || '-' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="fbsj" label="发布时间" width="130" align="center" />
<el-table-column prop="cjsj" label="创建时间" width="130" align="center" />
<el-table-column label="结束时间" width="130" align="center">
<el-table-column prop="fbsj" label="发布时间" width="150" align="center" :formatter="fmtDateTime" />
<el-table-column prop="jssj" label="结束时间" width="150" align="center" :formatter="fmtDateTime" />
<el-table-column prop="cjsj" label="创建时间" width="150" align="center" :formatter="fmtDateTime" />
<el-table-column prop="jcxqscsj" label="教材需求生成时间" width="170" align="center" :formatter="fmtDateTime" />
<el-table-column label="操作" width="200" align="center" fixed="right">
<template slot-scope="{ row }">
{{ row.jssj || '' }}
<el-button type="text" size="small" @click="handleDetail(row)">详情</el-button>
<el-button type="text" size="small" @click="handleEdit(row)">编辑</el-button>
<el-button
type="text"
size="small"
:disabled="row.zt === '发布'"
@click="handlePublish(row)"
>发布</el-button>
<el-button type="text" size="small" class="text-danger" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
<el-table-column prop="jysCount" label="教研室数" width="90" align="center" />
<el-table-column prop="teamCount" label="学员队数" width="90" align="center" />
<el-table-column prop="hbqKcCount" label="合班前课程任务数" width="140" align="center" />
<el-table-column prop="hbqXssCount" label="合班前总学时数" width="140" align="center" />
<el-table-column prop="hbhKcCount" label="合班后课程任务数" width="140" align="center" />
<el-table-column prop="hbhXssCount" label="合班后总学时数" width="140" align="center" />
</el-table>
<el-pagination
:current-page="pageNum"
:page-size="pageSize"
:total="total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
class="pagination"
@current-change="handlePageChange"
@size-change="handleSizeChange"
/>
</el-card>
<!-- ==================== 教学任务信息弹窗 ==================== -->
<!-- ==================== 4. 新增/修改对话框 ==================== -->
<el-dialog
:visible="dialogVisible"
title="教学任务信息"
width="900px"
class="teaching-task-dialog"
:title="dialogTitle"
width="560px"
:close-on-click-modal="false"
@update:visible="val => (dialogVisible = val)"
@update:visible="val => dialogVisible = val"
>
<!-- ==================== 1. 基本信息区域 ==================== -->
<el-form label-width="86px" class="basic-form">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="学期">
<el-input v-model="form.xq" readonly />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="任务名称">
<el-input v-model="form.rwmc" placeholder="请输入任务名称" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="创建时间">
<el-input v-model="form.cjsj" readonly />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="发布时间">
<el-input v-model="form.fbsj" readonly />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="结束时间">
<el-input v-model="form.jssj" readonly />
</el-form-item>
</el-col>
</el-row>
<el-form ref="formRef" :model="form" :rules="rules" label-width="140px" class="add-form">
<el-form-item v-if="isEdit" label="编号" prop="bh">
<el-input v-model="form.bh" disabled placeholder="编号不可修改" />
</el-form-item>
<el-form-item v-else label="编号">
<el-input disabled placeholder="新增后由系统自动生成" />
</el-form-item>
<el-form-item label="任务名称" prop="rwmc">
<el-input v-model="form.rwmc" placeholder="请输入任务名称" clearable />
</el-form-item>
<el-form-item label="年度" prop="nd">
<el-select v-model="form.nd" placeholder="请选择年度" style="width: 100%">
<el-option v-for="y in yearOptions" :key="y" :label="y" :value="y" />
</el-select>
</el-form-item>
<el-form-item label="状态" prop="zt">
<el-select v-model="form.zt" placeholder="请选择状态" style="width: 100%">
<el-option
v-for="opt in statusOptions"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-select>
</el-form-item>
<el-form-item label="发布时间">
<el-date-picker
v-model="form.fbsj"
type="datetime"
value-format="yyyy-MM-ddTHH:mm:ss"
placeholder="请选择发布时间"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="结束时间">
<el-date-picker
v-model="form.jssj"
type="datetime"
value-format="yyyy-MM-ddTHH:mm:ss"
placeholder="请选择结束时间"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="教材需求生成时间">
<el-date-picker
v-model="form.jcxqscsj"
type="datetime"
value-format="yyyy-MM-ddTHH:mm:ss"
placeholder="请选择教材需求生成时间"
style="width: 100%"
/>
</el-form-item>
</el-form>
<!-- ==================== 2. Tab 标签页区域 ==================== -->
<el-tabs v-model="activeTab" class="tt-tabs">
<!-- 教研室任务书列表 -->
<el-tab-pane label="教研室任务书列表" name="taskbook">
<div class="sub-toolbar">
<el-button type="primary" plain size="small" @click="handleTaskBookDetail">
教研室任务书详细
</el-button>
</div>
<el-table
ref="taskBookTable"
:data="taskBookRows"
border
stripe
highlight-current-row
class="taskbook-table"
@selection-change="handleTaskBookSelection"
>
<el-table-column type="selection" width="40" align="center" />
<el-table-column prop="jys" label="教研室" min-width="120" align="center" />
<el-table-column prop="zt" label="状态" width="80" align="center" />
<el-table-column prop="sbsj" label="上报时间" width="150" align="center" show-overflow-tooltip />
<el-table-column prop="xydkcCount" label="学员队课程任务数" width="150" align="center" />
<el-table-column prop="wczdsCount" label="完成指定数" width="100" align="center" />
</el-table>
</el-tab-pane>
<!-- 学员队列表 -->
<el-tab-pane label="学员队列表" name="team">
<div class="tab-empty">暂无学员队列表数据</div>
</el-tab-pane>
</el-tabs>
<!-- ==================== 3. 底部按钮区域 ==================== -->
<div slot="footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleConfirm">确定</el-button>
<el-button type="primary" :loading="saving" @click="handleSubmit">确定</el-button>
</div>
</el-dialog>
<!-- ==================== 5. 详情对话框 ==================== -->
<el-dialog
:visible="detailVisible"
title="教学任务详情"
width="620px"
:close-on-click-modal="false"
@update:visible="val => detailVisible = val"
>
<el-descriptions v-if="detailData.bh" :column="2" border>
<el-descriptions-item label="编号">{{ detailData.bh || '-' }}</el-descriptions-item>
<el-descriptions-item label="任务名称">{{ detailData.rwmc || '-' }}</el-descriptions-item>
<el-descriptions-item label="年度">{{ fmtVal(detailData.nd) }}</el-descriptions-item>
<el-descriptions-item label="状态">{{ detailData.zt || '-' }}</el-descriptions-item>
<el-descriptions-item label="发布时间">{{ fmtVal(detailData.fbsj) }}</el-descriptions-item>
<el-descriptions-item label="结束时间">{{ fmtVal(detailData.jssj) }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ fmtVal(detailData.cjsj) }}</el-descriptions-item>
<el-descriptions-item label="教材需求生成时间">{{ fmtVal(detailData.jcxqscsj) }}</el-descriptions-item>
</el-descriptions>
<div v-else v-loading="detailLoading" class="detail-empty">加载中...</div>
<div slot="footer">
<el-button type="primary" @click="detailVisible = false">关闭</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
/**
* 教学任务计划管理(完整管理页)
* 对应菜单:teachOffice/taskPlan
* 提供 teachingTask 全套真实接口操作:分页查询 list、详情 get?bh=、新增 add、编辑 update、
* 发布 batchPublish?bh=(需先填写教研室任务书)、删除 delete?bh=(级联删除教研室任务书)
* 只读查询视图在 src/views/teachBusiness/teachingTask/index.vue(教学任务列表)提供,避免重复操作同一实体
*/
import {
listTeachingTask,
getTeachingTask,
addTeachingTask,
updateTeachingTask,
deleteTeachingTask,
publishTeachingTask
} from '@/api/teachBusiness/teachingTask'
import { listAllSemester } from '@/api/teachBusiness/semester'
export default {
name: 'TaskPlan',
data() {
return {
// ==================== 1. 学期信息区域 ====================
semester: {
name: '2017年秋季学期', // 学期名称
kxrq: '2017-07-08', // 校历开学日期
jsrq: '2018-02-11', // 结束日期
weeks: 32 // 学期周数
// ==================== 1. 查询条件 ====================
searchForm: {
rwmc: '',
nd: undefined,
zt: ''
},
// ==================== 2. 数据表格 ====================
loading: false,
tableData: [
{
bh: 'task-2017-2',
rwmc: '2017年下半年教学任务',
zt: '发布',
fbsj: '2017年12月1日',
cjsj: '2017年7月8日',
jssj: '',
jysCount: 31,
teamCount: 78,
hbqKcCount: 1126,
hbqXssCount: 36050,
hbhKcCount: 1058,
hbhXssCount: 34050
},
{
bh: 'task-2019-1',
rwmc: '研究生下半年教学任务',
zt: '发布',
fbsj: '2019年12月1日',
cjsj: '2017年7月8日',
jssj: '',
jysCount: 12,
teamCount: 2,
hbqKcCount: 16,
hbqXssCount: 560,
hbhKcCount: 16,
hbhXssCount: 560
}
yearOptions: [],
statusOptions: [
{ label: '未发布', value: '未发布' },
{ label: '发布', value: '发布' }
],
selectedRows: [],
currentRow: null,
// ==================== 操作按钮 ====================
/** 教学任务信息弹窗 */
// ==================== 2. 表格数据 ====================
loading: false,
tableData: [],
pageNum: 1,
pageSize: 10,
total: 0,
// ==================== 3. 新增/修改表单 ====================
dialogVisible: false,
/** 弹窗模式:新建 / 编辑 */
dialogMode: 'create',
/** 编辑模式选中的任务行 */
editRow: null,
// ==================== 弹窗:基本信息 ====================
form: {
xq: '2017年秋季学期', // 学期(只读)
rwmc: '', // 任务名称(可编辑)
cjsj: '', // 创建时间(只读)
fbsj: '', // 发布时间(只读)
jssj: '' // 结束时间(只读)
dialogTitle: '新建教学任务',
isEdit: false,
saving: false,
form: this.createEmptyForm(),
rules: {
rwmc: [{ required: true, message: '请输入任务名称', trigger: 'blur' }],
nd: [{ required: true, message: '请选择年度', trigger: 'change' }],
zt: [{ required: true, message: '请选择状态', trigger: 'change' }]
},
// ==================== 弹窗:Tab 标签页 ====================
activeTab: 'taskbook',
// ==================== 弹窗:教研室任务书列表 ====================
taskBookRows: [],
selectedTaskBookRows: [],
/** 编辑模式下的教研室任务书数据(按截图还原) */
editTaskBookRows: [
{ jys: '军事81教研室', zt: '发布', sbsj: '', xydkcCount: 33, wczdsCount: 0 },
{ jys: '翻译室', zt: '发布', sbsj: '', xydkcCount: 2, wczdsCount: 0 },
{ jys: '军事共同教研室', zt: '上报', sbsj: '2019-12-13 10:39', xydkcCount: 97, wczdsCount: 8 },
{ jys: '作战保障教研室', zt: '发布', sbsj: '', xydkcCount: 47, wczdsCount: 0 },
{ jys: '军事10教研室', zt: '发布', sbsj: '', xydkcCount: 21, wczdsCount: 0 },
{ jys: '军事83', zt: '发布', sbsj: '', xydkcCount: 2, wczdsCount: 0 },
{ jys: '军事09教研室', zt: '发布', sbsj: '', xydkcCount: 52, wczdsCount: 0 }
]
// ==================== 4. 详情 ====================
detailVisible: false,
detailLoading: false,
detailData: {}
}
},
mounted() {
// 默认选中第一行(高亮)
this.$nextTick(() => {
if (this.tableData.length) {
this.$refs.tableRef.setCurrentRow(this.tableData[0])
}
})
created() {
this.loadYearOptions().then(() => this.fetchList())
},
methods: {
/* ---------- 年度下拉(数据来自 /semester/all,禁止硬编码) ---------- */
loadYearOptions() {
return listAllSemester().then(response => {
const list = response.data || []
const map = {}
list.forEach(item => {
if (item.nd !== null && item.nd !== undefined && item.nd !== '') map[item.nd] = item
})
const arr = Object.keys(map).sort((a, b) => String(b).localeCompare(String(a)))
this.yearOptions = arr
// 默认年度:优先当前学期(dqxq=true),否则取最新年度
const current = list.find(item => item.dqxq === true)
const defaultNd = (current && current.nd) || arr[0]
if (!this.searchForm.nd) this.searchForm.nd = defaultNd
return arr
}).catch(() => {
this.yearOptions = []
return []
})
},
watch: {
// 弹窗打开时按模式初始化
dialogVisible(val) {
if (!val) return
this.form.xq = this.semester.name
if (this.dialogMode === 'edit') {
this.form.rwmc = (this.editRow && this.editRow.rwmc) || ''
this.form.cjsj = '2017-07-14 17:00'
this.form.fbsj = '2019-12-13 10:39'
this.form.jssj = ''
this.taskBookRows = [...this.editTaskBookRows]
} else {
this.form.rwmc = '新研究生教学任务'
this.form.cjsj = ''
this.form.fbsj = ''
this.form.jssj = ''
this.taskBookRows = []
}
this.activeTab = 'taskbook'
// 默认选中第一行(高亮)
this.$nextTick(() => {
if (this.taskBookRows.length && this.$refs.taskBookTable) {
this.$refs.taskBookTable.setCurrentRow(this.taskBookRows[0])
/* ---------- 通用格式化 ---------- */
fmtDateTime(row, column, cellValue) {
if (cellValue === null || cellValue === undefined || cellValue === '') return '-'
return String(cellValue).replace('T', ' ').slice(0, 16)
},
fmtVal(val) {
if (val === null || val === undefined || val === '') return '-'
return String(val).replace('T', ' ').slice(0, 16)
},
/** 移除空值(''/null/undefined),保留 0 等有效值 */
cleanPayload(obj) {
const payload = {}
Object.keys(obj).forEach(key => {
const value = obj[key]
if (value !== '' && value !== null && value !== undefined) {
payload[key] = value
}
})
}
},
return payload
},
methods: {
// ==================== 表格 ====================
handleSelectionChange(rows) {
this.selectedRows = rows
},
handleCurrentChange(row) {
this.currentRow = row
},
/** 获取当前选中行,未选中时提示 */
getSelected() {
if (!this.currentRow) {
this.$message.warning('请先选择一条教学任务')
return null
/* ---------- 列表加载 ---------- */
fetchList() {
this.loading = true
const params = { pageNum: this.pageNum, pageSize: this.pageSize }
if (this.searchForm.rwmc && this.searchForm.rwmc.trim()) params.rwmc = this.searchForm.rwmc.trim()
if (this.searchForm.nd !== undefined && this.searchForm.nd !== null && this.searchForm.nd !== '') {
params.nd = this.searchForm.nd
}
if (this.searchForm.zt && this.searchForm.zt.trim()) params.zt = this.searchForm.zt.trim()
listTeachingTask(params).then(res => {
const data = (res && res.data) || {}
this.tableData = data.records || []
this.total = data.total || 0
this.loading = false
}).catch(() => {
this.tableData = []
this.total = 0
this.loading = false
})
},
handleQuery() {
this.pageNum = 1
this.fetchList()
},
handlePageChange(page) {
this.pageNum = page
this.fetchList()
},
handleSizeChange(size) {
this.pageSize = size
this.pageNum = 1
this.fetchList()
},
/* ---------- 新增 / 修改 ---------- */
createEmptyForm() {
return {
bh: '',
rwmc: '',
nd: undefined,
zt: '未发布',
fbsj: '',
jssj: '',
jcxqscsj: ''
}
return this.currentRow
},
// ==================== 操作按钮 ====================
/** 新建教学任务 */
handleAdd() {
this.dialogMode = 'create'
this.editRow = null
this.form = this.createEmptyForm()
if (!this.form.nd && this.searchForm.nd) this.form.nd = this.searchForm.nd
this.isEdit = false
this.dialogTitle = '新建教学任务'
this.dialogVisible = true
this.$nextTick(() => {
this.$refs.formRef && this.$refs.formRef.clearValidate()
})
},
/** 编辑教学任务 */
handleEdit() {
const row = this.getSelected()
if (!row) return
this.dialogMode = 'edit'
this.editRow = row
handleEdit(row) {
this.form = {
bh: row.bh || '',
rwmc: row.rwmc || '',
nd: row.nd,
zt: row.zt || '未发布',
fbsj: row.fbsj || '',
jssj: row.jssj || '',
jcxqscsj: row.jcxqscsj || ''
}
this.isEdit = true
this.dialogTitle = '修改教学任务'
this.dialogVisible = true
this.$nextTick(() => {
this.$refs.formRef && this.$refs.formRef.clearValidate()
})
},
/** 弹窗确认保存 */
handleDialogConfirm() {
// TODO: 调用保存接口
this.$message.success('已保存教学任务信息(前端演示)')
handleSubmit() {
this.$refs.formRef.validate(valid => {
if (!valid) {
this.$message.warning('请完善必填项后再提交')
return
}
const payload = this.cleanPayload(this.buildPayload())
this.saving = true
const req = this.isEdit ? updateTeachingTask(payload) : addTeachingTask(payload)
req.then(() => {
this.$message.success(this.isEdit ? '修改成功' : '新建成功')
this.dialogVisible = false
this.fetchList()
}).catch(() => {}).finally(() => {
this.saving = false
})
})
},
/** 删除教学任务 */
handleDelete() {
const row = this.getSelected()
if (!row) return
this.$confirm(`确定删除教学任务"${row.rwmc}"吗?删除后不可恢复。`, '删除确认', {
confirmButtonText: '确定删除',
/** 构建请求体:年度转数字以匹配后端 Integer,时间字段有值才传 */
buildPayload() {
const f = this.form
const payload = {}
if (this.isEdit && f.bh && String(f.bh).trim()) payload.bh = String(f.bh).trim()
if (f.rwmc && String(f.rwmc).trim()) payload.rwmc = String(f.rwmc).trim()
if (f.nd !== undefined && f.nd !== null && f.nd !== '') payload.nd = Number(f.nd)
if (f.zt && String(f.zt).trim()) payload.zt = String(f.zt).trim()
if (f.fbsj) payload.fbsj = f.fbsj
if (f.jssj) payload.jssj = f.jssj
if (f.jcxqscsj) payload.jcxqscsj = f.jcxqscsj
return payload
},
/* ---------- 详情 ---------- */
handleDetail(row) {
this.detailVisible = true
this.detailLoading = true
this.detailData = {}
getTeachingTask(row.bh).then(res => {
this.detailData = (res && res.data) || {}
this.detailLoading = false
}).catch(() => {
this.detailLoading = false
})
},
/* ---------- 删除 ---------- */
handleDelete(row) {
this.$confirm(`确定要删除「${row.rwmc || row.bh || '该记录'}」吗?删除将级联删除关联的教研室任务书。`, '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
// TODO: 调用删除接口
this.$message.success('删除成功(前端演示)')
})
.catch(() => {})
},
/** 发布教学任务 */
handlePublish() {
const row = this.getSelected()
if (!row) return
// TODO: 调用发布接口
this.$message.success(`已发布教学任务:${row.rwmc}`)
},
/** 结束发布 */
handleEndPublish() {
const row = this.getSelected()
if (!row) return
// TODO: 调用结束发布接口
this.$message.success(`已结束发布:${row.rwmc}`)
},
/** 管理教学任务所含学员队 */
handleManageTeams() {
const row = this.getSelected()
if (!row) return
// TODO: 学员队管理弹窗,待接口接入
this.$message.success(`管理教学任务所含学员队:${row.rwmc}`)
}).then(() => deleteTeachingTask(row.bh)).then(() => {
this.$message.success('删除成功')
this.fetchList()
}).catch(() => {})
},
// ==================== 弹窗 ====================
handleTaskBookSelection(rows) {
this.selectedTaskBookRows = rows
},
/** 打开教研室任务书详细(前端演示) */
handleTaskBookDetail() {
// TODO: 教研室任务书详细弹窗,待接口接入
},
/** 弹窗确定 */
handleConfirm() {
this.handleDialogConfirm()
this.dialogVisible = false
/* ---------- 发布 ---------- */
handlePublish(row) {
this.$confirm(`确定要发布教学任务「${row.rwmc || row.bh || '该记录'}」吗?发布前请先填写关联的教研室任务书。`, '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => publishTeachingTask(row.bh)).then(() => {
this.$message.success('发布成功')
this.fetchList()
}).catch(() => {})
}
}
}
@@ -382,7 +446,15 @@ export default {
}
.task-plan-page {
// ========== 1. 学期信息区域 ==========
.page-title {
text-align: center;
font-size: 20px;
font-weight: 600;
color: #303133;
margin-bottom: 20px;
}
// ========== 1. 查询条件区域 ==========
.search-card {
margin-bottom: 16px;
@@ -394,17 +466,7 @@ export default {
}
}
// ========== 2. 列表标题 ==========
.list-title {
font-size: 16px;
font-weight: 600;
color: #303133;
margin-bottom: 12px;
padding-left: 10px;
border-left: 4px solid var(--edu-green-primary);
}
// ========== 3. 工具栏 ==========
// ========== 2. 工具栏 ==========
.list-toolbar {
display: flex;
align-items: center;
@@ -422,66 +484,30 @@ export default {
}
}
// ========== 4. 数据表格区域 ==========
// ========== 3. 数据表格区域 ==========
.table-card {
.task-table {
width: 100%;
}
.status-text {
color: var(--edu-green-primary);
font-weight: 600;
}
.pagination {
display: flex;
justify-content: flex-end;
margin-top: 12px;
}
// 当前行高亮
::v-deep(.el-table__body tr.current-row > td.el-table__cell) {
background-color: #e1f5ec;
}
::v-deep(.el-table__body tr.current-row:hover > td.el-table__cell) {
background-color: #e1f5ec;
}
.text-danger {
color: #f56c6c;
}
}
// ========== 4. 教学任务信息弹窗 ==========
.teaching-task-dialog {
.basic-form {
padding-bottom: 8px;
border-bottom: 1px solid #ebeef5;
margin-bottom: 4px;
}
.tt-tabs {
::v-deep(.el-tabs__header) {
margin-bottom: 12px;
}
.sub-toolbar {
margin-bottom: 12px;
}
.taskbook-table {
width: 100%;
// 当前行高亮
::v-deep(.el-table__body tr.current-row > td.el-table__cell) {
background-color: #e1f5ec;
}
::v-deep(.el-table__body tr.current-row:hover > td.el-table__cell) {
background-color: #e1f5ec;
}
}
.tab-empty {
min-height: 200px;
display: flex;
align-items: center;
justify-content: center;
color: #909399;
font-size: 13px;
}
}
// ========== 4. 详情 ==========
.detail-empty {
min-height: 120px;
display: flex;
align-items: center;
justify-content: center;
color: #909399;
}
}
</style>
File diff suppressed because it is too large Load Diff
@@ -2,7 +2,6 @@
<div class="page-container">
<!-- 查询条件 -->
<el-card shadow="never" class="search-card">
<div class="search-tip">勾选"选择框"表示启用该项对应的查询条件。</div>
<el-form :model="searchForm" label-width="110px" class="search-form">
<el-row :gutter="0">
<el-col :span="12">
@@ -2,7 +2,6 @@
<div class="page-container">
<!-- 查询条件 -->
<el-card shadow="never" class="search-card">
<div class="search-tip">勾选"选择框"表示启用该项对应的查询条件。</div>
<el-form :model="searchForm" label-width="110px" class="search-form">
<el-row :gutter="0">
<el-col :span="12">
@@ -221,9 +220,7 @@
import { formatDate } from '@/utils/index'
import {
getTeachingLogList,
deleteTeachingLog,
batchReportTeachingLog,
recheckTeachingLog,
submitTeachingLog,
exportTeachingLog,
importTeachingLogExcel,
downloadTeachingLogTemplate,
@@ -380,43 +377,36 @@ export default {
},
handleDeleteSelected() {
const ids = this.getSelectedBhs()
if (ids.length === 0) return
this.$confirm(`确定要删除选中的 ${ids.length} 条记录吗?`, '删除确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => deleteTeachingLog(ids))
.then(() => {
this.$message.success(`已删除 ${ids.length} 条记录`)
this.fetchList()
})
.catch(() => {})
// 后端暂未提供删除接口,仅作提示
this.$message.info('后端暂未提供该接口')
},
handleBatchReport() {
const ids = this.getSelectedBhs()
if (ids.length === 0) return
this.$confirm(`确定要批量上报选中的 ${ids.length} 条记录吗?`, '上报确认', {
this.$confirm(`确定要上报选中的 ${ids.length} 条记录吗?`, '上报确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => batchReportTeachingLog(ids))
.then(() => {
this.$message.success(`已批量上报 ${ids.length} 条记录`)
// 提交教学日志审核,逐条上报至「已上报」状态
let settled = Promise.resolve()
ids.forEach((bh) => {
settled = settled.then(() => submitTeachingLog(bh, '已上报'))
})
return settled
})
.then(() => {
this.$message.success(`已上报 ${ids.length} 条记录`)
this.fetchList()
})
.catch(() => {})
},
handleRecheck() {
recheckTeachingLog()
.then(() => {
this.$message.success('重新检查完成')
})
.catch(() => {})
// 后端暂未提供重新检查接口,仅作提示
this.$message.info('后端暂未提供该接口')
},
downloadBlob(blob, fileName) {
@@ -2,7 +2,6 @@
<div class="page-container">
<!-- 查询条件 -->
<el-card shadow="never" class="search-card">
<div class="search-tip">勾选"选择框"表示启用该项对应的查询条件。</div>
<el-form :model="searchForm" label-width="110px" class="search-form">
<el-row :gutter="0">
<el-col :span="12">
@@ -208,10 +207,8 @@
import { formatDate } from '@/utils/index'
import {
getReportedTeachingLogList,
deleteTeachingLog,
batchAuditTeachingLog,
approveTeachingLog,
rejectTeachingLog,
recheckTeachingLog,
exportTeachingLog,
getTeachingLogByBh
} from '@/api/log'
@@ -375,19 +372,8 @@ export default {
},
handleDelete() {
const ids = this.getSelectedBhs()
if (ids.length === 0) return
this.$confirm(`确定要删除选中的 ${ids.length} 条记录吗?`, '删除确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => deleteTeachingLog(ids))
.then(() => {
this.$message.success(`已删除 ${ids.length} 条记录`)
this.fetchList()
})
.catch(() => {})
// 后端暂未提供删除接口,仅作提示
this.$message.info('后端暂未提供该接口')
},
handleAudit() {
@@ -398,7 +384,14 @@ export default {
cancelButtonText: '取消',
type: 'warning'
})
.then(() => batchAuditTeachingLog(ids))
.then(() => {
// 审核通过教学日志,逐条审核
let settled = Promise.resolve()
ids.forEach((bh) => {
settled = settled.then(() => approveTeachingLog(bh, ''))
})
return settled
})
.then(() => {
this.$message.success(`已审核通过 ${ids.length} 条记录`)
this.fetchList()
@@ -432,11 +425,8 @@ export default {
},
handleRecheck() {
recheckTeachingLog()
.then(() => {
this.$message.success('重新检查变更情况完成')
})
.catch(() => {})
// 后端暂未提供重新检查接口,仅作提示
this.$message.info('后端暂未提供该接口')
},
downloadBlob(blob, fileName) {
File diff suppressed because it is too large Load Diff