Main #2

Merged
zhaichao merged 3 commits from zhaichao/education:main into main 2026-08-24 07:39:19 +00:00
15 changed files with 508 additions and 519 deletions
@@ -1,81 +0,0 @@
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'
})
}
+68
View File
@@ -0,0 +1,68 @@
import request from '@/utils/request'
// 分页查询选修班
// pageNum/pageSize/jyxm(通信)/kmc(通信)/kxbcnj(通信)/xxbmc(通信)/xkzt(等值)/pxcc(等值)
export function listElective(query) {
return request({
url: '/jys/elective/list',
method: 'get',
params: query
})
}
// 批量开放报名
export function batchOpenElective(bhList) {
return request({
url: '/jys/elective/batch-open',
method: 'post',
data: { bhList: bhList }
})
}
// 批量取消开放报名
export function batchCancelOpenElective(bhList) {
return request({
url: '/jys/elective/batch-cancel-open',
method: 'post',
data: { bhList: bhList }
})
}
// 批量停止报名
export function batchStopElective(bhList) {
return request({
url: '/jys/elective/batch-stop',
method: 'post',
data: { bhList: bhList }
})
}
// 下载选修班列表(Excel
export function exportElectiveList(query) {
return request({
url: '/jys/elective/export',
method: 'get',
params: query,
responseType: 'blob'
})
}
// 下载选修班学员名单(Excel)
export function exportElectiveStudentsExcel(bhList) {
return request({
url: '/jys/elective/students/export-excel',
method: 'get',
params: { bhList: bhList },
responseType: 'blob'
})
}
// 下载选修班学员名单(Word
export function exportElectiveStudentsWord(bhList) {
return request({
url: '/jys/elective/students/export-word',
method: 'get',
params: { bhList: bhList },
responseType: 'blob'
})
}
+47
View File
@@ -0,0 +1,47 @@
import request from '@/utils/request'
// 分页查询课程科目(课表 KB
// kmc/kmdm/jc 模糊,jysdh/kclx/pxlx/pxcc 等值
export function listKb(query) {
return request({
url: '/jys/kb/list',
method: 'get',
params: query
})
}
// 课程科目详情(kbh 课编号)
export function getKb(kbh) {
return request({
url: '/jys/kb/get',
method: 'get',
params: { kbh: kbh }
})
}
// 新增课程科目(kbh 留空后端生成 UUID)
export function addKb(data) {
return request({
url: '/jys/kb/add',
method: 'post',
data: data
})
}
// 删除课程科目(kbh 课编号)
export function deleteKb(kbh) {
return request({
url: '/jys/kb/delete',
method: 'post',
params: { kbh: kbh }
})
}
// 编辑课程科目(kbh 必传)
export function updateKb(data) {
return request({
url: '/jys/kb/update',
method: 'post',
data: data
})
}
-70
View File
@@ -1,70 +0,0 @@
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
})
}
+2 -2
View File
@@ -114,10 +114,10 @@
} }
} }
// 表格横向拖拽滚动:拖拽过程中保持默认光标,不显示“抓手”,避免干扰操作手感 // 表格横向拖拽滚动:拖拽过程中光标切换为“抓手”
.el-table__body-wrapper.is-dragging, .el-table__body-wrapper.is-dragging,
.el-table__fixed-body-wrapper.is-dragging { .el-table__fixed-body-wrapper.is-dragging {
cursor: default; cursor: grabbing;
} }
} }
+210 -2
View File
@@ -1,8 +1,89 @@
<template> <template>
<div class="app-container home"> <div class="app-container home">
<!-- 通知公告 --> <!-- 第一行信息/日志/公告 -->
<el-row :gutter="20" class="bottom-row"> <el-row :gutter="20" class="bottom-row">
<el-col :xs="24" :sm="24" :md="24" :lg="24"> <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-card class="module-card notice-card" shadow="hover"> <el-card class="module-card notice-card" shadow="hover">
<div slot="header" class="card-header"> <div slot="header" class="card-header">
<span><i class="el-icon-bell card-icon"></i> 通知公告</span> <span><i class="el-icon-bell card-icon"></i> 通知公告</span>
@@ -50,6 +131,7 @@
<script> <script>
import { listNotice } from "@/api/system/notice" import { listNotice } from "@/api/system/notice"
import { homeList } from "@/api/monitor/logininfor"
import NoticeDetailView from "@/layout/components/HeaderNotice/DetailView" import NoticeDetailView from "@/layout/components/HeaderNotice/DetailView"
export default { export default {
@@ -60,6 +142,13 @@ export default {
version: "3.9.2", version: "3.9.2",
noticeList: [], noticeList: [],
noticeLoading: false, noticeLoading: false,
loginLogList: [],
loginLogLoading: false,
loginLogPage: {
currentPage: 1,
pageSize: 10,
total: 0
},
noticePage: { noticePage: {
currentPage: 1, currentPage: 1,
pageSize: 10, pageSize: 10,
@@ -69,6 +158,7 @@ export default {
}, },
mounted() { mounted() {
this.loadNoticeList() this.loadNoticeList()
this.loadLoginLogList()
}, },
methods: { methods: {
goTarget(href) { goTarget(href) {
@@ -118,6 +208,28 @@ export default {
handleViewNotice(item) { handleViewNotice(item) {
this.$refs.noticeViewRef.open(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) { handleNoticeSizeChange(val) {
this.noticePage.pageSize = val this.noticePage.pageSize = val
this.noticePage.currentPage = 1 this.noticePage.currentPage = 1
@@ -259,6 +371,102 @@ 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: center;
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-list {
.notice-item { .notice-item {
padding: 14px 0; padding: 14px 0;
@@ -327,10 +327,6 @@ export default {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.app-container {
padding: 20px;
}
.plan-import-page { .plan-import-page {
.search-card { .search-card {
margin-bottom: 16px; margin-bottom: 16px;
@@ -408,12 +404,6 @@ export default {
.import-table { .import-table {
width: 100%; width: 100%;
} }
.pagination-wrapper {
display: flex;
justify-content: flex-end;
margin-top: 16px;
}
} }
} }
</style> </style>
@@ -420,19 +420,9 @@ export default {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.app-container {
padding: 20px;
}
.student-info-page { .student-info-page {
.search-card { .search-card {
margin-bottom: 16px;
.search-form { .search-form {
.w-full {
width: 100%;
}
.range-control { .range-control {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -466,13 +456,6 @@ export default {
} }
} }
.list-title {
font-size: 16px;
font-weight: 600;
color: #303133;
margin-bottom: 12px;
}
.toolbar { .toolbar {
margin-bottom: 16px; margin-bottom: 16px;
} }
@@ -481,12 +464,6 @@ export default {
.el-table { .el-table {
width: 100%; width: 100%;
} }
.pagination {
display: flex;
justify-content: flex-end;
margin-top: 16px;
}
} }
.text-primary { .text-primary {
@@ -494,15 +494,7 @@ export default {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.app-container {
padding: 20px;
}
.discipline-page { .discipline-page {
.search-card {
margin-bottom: 16px;
}
.search-form { .search-form {
.w-full { .w-full {
width: 100%; width: 100%;
@@ -547,12 +539,6 @@ export default {
color: #303133; color: #303133;
} }
} }
.pagination-wrapper {
display: flex;
justify-content: flex-end;
margin-top: 16px;
}
} }
.add-form { .add-form {
@@ -115,8 +115,8 @@ export default {
totalWeeks: 0, totalWeeks: 0,
dateRangeText: '', dateRangeText: '',
weeks: [], weeks: [],
// 显示控制:默认勾选显示日期;节次全部展示 // 显示控制:默认勾选显示日期;节次全部展示
showDate: false, showDate: true,
// 事件存储:key = cellKey -> event // 事件存储:key = cellKey -> event
events: {}, events: {},
loading: false loading: false
@@ -1,7 +1,7 @@
<template> <template>
<div class="app-container teach-semester"> <div class="app-container teach-semester">
<!-- 工具栏 --> <!-- 工具栏 -->
<el-row :gutter="10" class="semester-toolbar"> <el-row :gutter="10" class="mb8">
<el-col :span="1.5"> <el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd">新添</el-button> <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd">新添</el-button>
</el-col> </el-col>
@@ -17,61 +17,49 @@
</el-row> </el-row>
<!-- 数据表格 --> <!-- 数据表格 -->
<div class="semester-table-wrap">
<el-table <el-table
v-loading="loading" v-loading="loading"
:data="semesterList" :data="semesterList"
border border
stripe
:height="tableHeight" :height="tableHeight"
:row-class-name="tableRowClassName"
:header-cell-style="{ background: '#F4F8F5', color: '#303133', fontWeight: 600 }"
@selection-change="handleSelectionChange" @selection-change="handleSelectionChange"
> >
<el-table-column type="selection" align="center" width="50" /> <el-table-column type="selection" align="center" width="50" />
<el-table-column label="学期名称" align="center" min-width="190"> <el-table-column label="学期名称" align="center" min-width="190">
<template slot-scope="scope"> <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>{{ getSemesterName(scope.row.nd) }}</span>
</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="当前" align="center" width="80"> <el-table-column label="当前" align="center" width="70">
<template slot-scope="scope"> <template slot-scope="scope">
<el-tag v-if="scope.row.dqxq" type="success" effect="dark" size="mini">当前学期</el-tag> <el-tag v-if="scope.row.dqxq" type="success" size="mini">当前</el-tag>
<span v-else class="text-muted"></span> <span v-else>-</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="开学日期" align="center" min-width="120"> <el-table-column label="开学日期" align="center" prop="kxrq" min-width="130">
<template slot-scope="scope"> <template slot-scope="scope">
<span class="date-cell"><i class="el-icon-date"></i>{{ formatDate(scope.row.kxrq) || '—' }}</span> <span>{{ formatDate(scope.row.kxrq) }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="结束日期" align="center" min-width="120"> <el-table-column label="结束日期" align="center" prop="jsrq" min-width="130">
<template slot-scope="scope"> <template slot-scope="scope">
<span class="date-cell"><i class="el-icon-date"></i>{{ formatDate(scope.row.jsrq) || '—' }}</span> <span>{{ formatDate(scope.row.jsrq) }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="周数" align="center" prop="sdzs" width="80"> <el-table-column label="周数" align="center" prop="sdzs" width="70" />
<el-table-column label="调课不审批" align="center" width="100">
<template slot-scope="scope"> <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> <el-tag :type="scope.row.tkbsp ? 'primary' : 'info'" size="mini">{{ scope.row.tkbsp ? '是' : '否' }}</el-tag>
<span v-else class="text-muted"></span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="调课不审批" align="center" width="116"> <el-table-column label="禁止调课" align="center" width="90">
<template slot-scope="scope"> <template slot-scope="scope">
<span :class="boolCellClass(scope.row.tkbsp)">{{ scope.row.tkbsp ? '是' : '否' }}</span> <el-tag :type="scope.row.jztk ? 'danger' : 'info'" size="mini">{{ scope.row.jztk ? '是' : '否' }}</el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="禁止调课" align="center" width="100"> <el-table-column label="终结成绩定及格" align="center" width="120">
<template slot-scope="scope"> <template slot-scope="scope">
<span :class="boolCellClass(scope.row.jztk)">{{ scope.row.jztk ? '是' : '否' }}</span> <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" width="132">
<template slot-scope="scope">
<span :class="boolCellClass(scope.row.zjcjdjg)">{{ scope.row.zjcjdjg ? '是' : '否' }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="课时系数方案" align="center" min-width="120"> <el-table-column label="课时系数方案" align="center" min-width="120">
@@ -79,13 +67,12 @@
<span>{{ getCoefficientLabel(scope.row.ksxsfabh) }}</span> <span>{{ getCoefficientLabel(scope.row.ksxsfabh) }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="锁定时长" align="center" min-width="96"> <el-table-column label="锁定时长" align="center" min-width="100">
<template slot-scope="scope"> <template slot-scope="scope">
<span class="lock-cell">{{ scope.row.sdjldw || '' }}</span> <span>{{ scope.row.sdjldw || '-' }}</span>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
</div>
<pagination <pagination
v-show="total > 0" v-show="total > 0"
@@ -348,14 +335,6 @@ export default {
this.loading = false 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) { handleSelectionChange(selection) {
this.ids = selection.map(item => item.nd) this.ids = selection.map(item => item.nd)
@@ -512,146 +491,8 @@ export default {
<style scoped lang="scss"> <style scoped lang="scss">
.teach-semester { .teach-semester {
/* 页面标题区 */ .mb8 {
.semester-header { margin-bottom: 8px;
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 文字换行、字段拥挤换行 */ /* 弹窗内表单:防止 label 文字换行、字段拥挤换行 */
@@ -242,7 +242,7 @@
import { listTeachingPlan } from '@/api/teachBusiness/courseRunning' import { listTeachingPlan } from '@/api/teachBusiness/courseRunning'
import { listStudentTeamTask } from '@/api/teachBusiness/studentTeamTask' import { listStudentTeamTask } from '@/api/teachBusiness/studentTeamTask'
import { listTeam } from '@/api/studentRecords/team' import { listTeam } from '@/api/studentRecords/team'
import { listSubject } from '@/api/teachOffice/subject' import { listKb } from '@/api/teachOffice/kb'
import { listTeacher } from '@/api/teachOffice/teacher' import { listTeacher } from '@/api/teachOffice/teacher'
import { listAllSemester } from '@/api/teachBusiness/semester' import { listAllSemester } from '@/api/teachBusiness/semester'
@@ -516,7 +516,7 @@ export default {
}) })
}, },
loadKbMap() { loadKbMap() {
listSubject({ pageNum: 1, pageSize: 1000 }).then(response => { listKb({ pageNum: 1, pageSize: 1000 }).then(response => {
const data = response.data || {} const data = response.data || {}
const map = {} const map = {}
;(data.records || []).forEach(item => { ;(data.records || []).forEach(item => {
+116 -93
View File
@@ -88,17 +88,17 @@
<!-- ==================== 6. 数据表格区域 ==================== --> <!-- ==================== 6. 数据表格区域 ==================== -->
<div class="section-card table-card"> <div class="section-card table-card">
<el-table :data="pagedData" border stripe class="main-table" @selection-change="handleSelectionChange"> <el-table v-loading="loading" :data="tableData" border stripe class="main-table" @selection-change="handleSelectionChange">
<template slot="empty"> <template slot="empty">
<span>无数据</span> <span>无数据</span>
</template> </template>
<el-table-column type="selection" width="40" align="center" /> <el-table-column type="selection" width="40" align="center" />
<el-table-column type="index" label="序号" width="60" align="center" :index="tableIndex" /> <el-table-column type="index" label="序号" width="60" align="center" :index="tableIndex" />
<el-table-column prop="course" label="课程" min-width="110" show-overflow-tooltip /> <el-table-column prop="kmc" label="课程" min-width="110" show-overflow-tooltip />
<el-table-column prop="textbook" label="教材" min-width="120" show-overflow-tooltip /> <el-table-column prop="jcmc" label="教材" min-width="120" show-overflow-tooltip />
<el-table-column prop="teacher" label="实施教员" width="90" align="center" /> <el-table-column prop="jyxm" label="实施教员" width="90" align="center" />
<el-table-column prop="place" label="教学场地" width="100" show-overflow-tooltip /> <el-table-column prop="jsmc" label="教学场地" width="100" show-overflow-tooltip />
<el-table-column prop="teamClass" label="显示可选队别班次" min-width="150" align="center" show-overflow-tooltip /> <el-table-column prop="kxdlbc" label="显示可选队别班次" min-width="150" align="center" show-overflow-tooltip />
<el-table-column label="计划学时/运行学时/学分" width="130" align="center"> <el-table-column label="计划学时/运行学时/学分" width="130" align="center">
<template slot="header"> <template slot="header">
<div class="triple-header"> <div class="triple-header">
@@ -109,31 +109,29 @@
</template> </template>
<template slot-scope="{ row }"> <template slot-scope="{ row }">
<div class="triple-cell"> <div class="triple-cell">
<span>{{ row.planHours }}</span> <span>{{ row.jhyxxsxf }}</span>
<span>{{ row.runHours }}</span>
<span>{{ row.credit }}</span>
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="开课结课日期" min-width="150" align="center"> <el-table-column label="开课结课日期" min-width="150" align="center">
<template slot-scope="{ row }"> <template slot-scope="{ row }">
{{ row.startDate }} ~ {{ row.endDate }} {{ row.kkjkrq }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="报名日期" min-width="140" align="center"> <el-table-column label="报名日期" min-width="140" align="center">
<template slot-scope="{ row }"> <template slot-scope="{ row }">
{{ row.signupStart }} ~ {{ row.signupEnd }} {{ row.bmrq }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="planCount" label="计划人数" width="90" align="center" /> <el-table-column prop="jhrs" label="计划人数" width="90" align="center" />
<el-table-column prop="requirement" label="要求说明" min-width="120" show-overflow-tooltip /> <el-table-column prop="yqsm" label="要求说明" min-width="120" show-overflow-tooltip />
</el-table> </el-table>
<div class="pagination-wrap"> <div class="pagination-wrap">
<el-pagination <el-pagination
background background
layout="total, prev, pager, next, jumper" layout="total, prev, pager, next, jumper"
:total="tableData.length" :total="total"
:page-size="pageSize" :page-size="pageSize"
:current-page="currentPage" :current-page="currentPage"
@current-change="handlePageChange" @current-change="handlePageChange"
@@ -183,6 +181,17 @@
</template> </template>
<script> <script>
import {
listElective,
batchOpenElective,
batchCancelOpenElective,
batchStopElective,
exportElectiveList,
exportElectiveStudentsExcel,
exportElectiveStudentsWord
} from '@/api/teachOffice/elective'
import { saveAs } from 'file-saver'
export default { export default {
name: 'Elective', name: 'Elective',
data() { data() {
@@ -203,14 +212,10 @@ export default {
pxcc: '无' pxcc: '无'
}, },
// ==================== 数据表格 mock ==================== // ==================== 数据表格 ====================
tableData: [ tableData: [],
{ id: 1, course: '军事英语', textbook: '军事英语教程', teacher: '张三', place: '教室101', teamClass: '2025级学员1-5队', planHours: 32, runHours: 30, credit: 2, startDate: '2026-09-01', endDate: '2027-01-15', signupStart: '2026-08-20', signupEnd: '2026-09-01', planCount: 60, requirement: '限选50人' }, total: 0,
{ id: 2, course: '摄影技术', textbook: '摄影基础', teacher: '李四', place: '教室102', teamClass: '2024级学员1-4队', planHours: 24, runHours: 24, credit: 1.5, startDate: '2026-09-01', endDate: '2027-01-15', signupStart: '2026-08-20', signupEnd: '2026-09-01', planCount: 40, requirement: '自备相机' }, loading: false,
{ id: 3, course: '网球', textbook: '网球教程', teacher: '王五', place: '操场', teamClass: '2025级学员2-6队', planHours: 24, runHours: 22, credit: 1.5, startDate: '2026-09-01', endDate: '2027-01-15', signupStart: '2026-08-21', signupEnd: '2026-09-02', planCount: 30, requirement: '需自备球拍' },
{ id: 4, course: '书法鉴赏', textbook: '中国书法', teacher: '赵六', place: '教室103', teamClass: '2026级学员1-3队', planHours: 16, runHours: 16, credit: 1, startDate: '2026-09-08', endDate: '2026-12-30', signupStart: '2026-08-25', signupEnd: '2026-09-05', planCount: 35, requirement: '' },
{ id: 5, course: '无人机操作', textbook: '无人机原理', teacher: '孙七', place: '室外训练场', teamClass: '2025级学员1-6队', planHours: 32, runHours: 28, credit: 2, startDate: '2026-09-08', endDate: '2027-01-10', signupStart: '2026-08-26', signupEnd: '2026-09-06', planCount: 25, requirement: '需通过体检' }
],
selectedRows: [], selectedRows: [],
// ==================== 文件上传 ==================== // ==================== 文件上传 ====================
@@ -241,73 +246,124 @@ export default {
computed: { computed: {
fileName() { fileName() {
return (this.selectedFile && this.selectedFile.name) || '未选择任何文件' return (this.selectedFile && this.selectedFile.name) || '未选择任何文件'
},
pagedData() {
const start = (this.currentPage - 1) * this.pageSize
return this.tableData.slice(start, start + this.pageSize)
} }
}, },
created() {
this.loadList()
},
methods: { methods: {
// 组装查询过滤条件(列表与导出共用,导出不含分页参数)
buildFilter() {
const q = {}
if (this.searchForm.js) q.jyxm = this.searchForm.js
if (this.searchForm.kbcnj) q.kxbcnj = this.searchForm.kbcnj
if (this.searchForm.kckmmc) q.kmc = this.searchForm.kckmmc
if (this.searchForm.xxbmc) q.xxbmc = this.searchForm.xxbmc
if (this.searchForm.xxkztEnabled) q.xkzt = this.searchForm.xxkzt
if (this.searchForm.pxccEnabled) q.pxcc = this.searchForm.pxcc
return q
},
// ==================== 查询 ==================== // ==================== 查询 ====================
handleQuery() { handleQuery() {
console.log('查询条件:', JSON.parse(JSON.stringify(this.searchForm))) this.currentPage = 1
this.$message.success('查询完成(前端模拟)') this.loadList()
},
loadList() {
this.loading = true
const params = Object.assign({ pageNum: this.currentPage, pageSize: this.pageSize }, this.buildFilter())
listElective(params)
.then(res => {
const data = (res && res.data) || {}
this.tableData = data.records || []
this.total = data.total || 0
})
.finally(() => {
this.loading = false
})
}, },
// ==================== 导出 ==================== // ==================== 导出 ====================
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)
},
handleDownloadList() { handleDownloadList() {
this.downloadBlob('课程,教材,实施教员,教学场地,计划人数\n', '选修班列表.txt') exportElectiveList(this.buildFilter())
this.$message.success('选修班列表已下载(前端模拟)') .then(blob => {
saveAs(blob, '选修班列表.xlsx')
this.$message.success('选修班列表已下载')
})
.catch(() => {})
}, },
handleDownloadWord() { handleDownloadWord() {
this.downloadBlob('选修班学员名单\n', '选修班学员名单Word.txt') const bhList = this.selectedRows.map(row => row.bh)
this.$message.success('选修班学员名单Word已下载(前端模拟)') if (bhList.length === 0) {
this.$message.warning('请先选择选修班')
return
}
exportElectiveStudentsWord(bhList)
.then(blob => {
saveAs(blob, '选修班学员名单.docx')
this.$message.success('选修班学员名单Word已下载')
})
.catch(() => {})
}, },
handleDownloadExcel() { handleDownloadExcel() {
this.downloadBlob('选修班学员名单\n', '选修班学员名单Excel.txt') const bhList = this.selectedRows.map(row => row.bh)
this.$message.success('选修班学员名单Excel已下载(前端模拟)') if (bhList.length === 0) {
}, this.$message.warning('请先选择选修班')
handleDownloadTemplate() { return
this.downloadBlob('课程,教材,实施教员,教学场地,计划学时,学分,开课结课日期,报名日期,计划人数,要求说明\n', '选修课数据文件模板.txt') }
this.$message.success('【选修课数据文件模板】已下载(前端模拟)') exportElectiveStudentsExcel(bhList)
.then(blob => {
saveAs(blob, '选修班学员名单.xlsx')
this.$message.success('选修班学员名单Excel已下载')
})
.catch(() => {})
}, },
// ==================== 操作按钮 ==================== // ==================== 操作按钮 ====================
handleOpenEarly() { handleOpenEarly() {
if (this.selectedRows.length === 0) { const bhList = this.selectedRows.map(row => row.bh)
if (bhList.length === 0) {
this.$message.warning('请先选择选修班') this.$message.warning('请先选择选修班')
return return
} }
this.$message.success('所选班已早开放报名(前端模拟)') batchOpenElective(bhList)
.then(() => {
this.$message.success('所选班已开放报名')
this.loadList()
})
.catch(() => {})
}, },
handleCancelEarly() { handleCancelEarly() {
if (this.selectedRows.length === 0) { const bhList = this.selectedRows.map(row => row.bh)
if (bhList.length === 0) {
this.$message.warning('请先选择选修班') this.$message.warning('请先选择选修班')
return return
} }
this.$message.success('所选班已早取消开放报名(前端模拟)') batchCancelOpenElective(bhList)
.then(() => {
this.$message.success('所选班已取消开放报名')
this.loadList()
})
.catch(() => {})
}, },
handleStopSignup() { handleStopSignup() {
if (this.selectedRows.length === 0) { const bhList = this.selectedRows.map(row => row.bh)
if (bhList.length === 0) {
this.$message.warning('请先选择选修班') this.$message.warning('请先选择选修班')
return return
} }
this.$message.success('已批量停止报名(前端模拟)') batchStopElective(bhList)
.then(() => {
this.$message.success('已批量停止报名')
this.loadList()
})
.catch(() => {})
}, },
handleReverseRange() { handleReverseRange() {
if (this.selectedRows.length === 0) { this.$message.warning('后端暂未提供该接口')
this.$message.warning('请先选择选修班') },
return handleDownloadTemplate() {
} this.$message.warning('后端暂未提供该接口')
this.$message.success('已根据学员反向确定班次范围(前端模拟)')
}, },
// ==================== 文件上传 ==================== // ==================== 文件上传 ====================
@@ -322,8 +378,7 @@ export default {
this.$message.warning('请先选择文件') this.$message.warning('请先选择文件')
return return
} }
console.log('上传文件:', this.selectedFile.name) this.$message.warning('后端暂未提供该接口')
this.$message.success(`文件「${this.selectedFile.name}」上传成功(前端模拟)`)
}, },
// ==================== 表格多选 ==================== // ==================== 表格多选 ====================
@@ -333,8 +388,7 @@ export default {
// ==================== 新建选修课弹窗 ==================== // ==================== 新建选修课弹窗 ====================
handleOpenNew() { handleOpenNew() {
Object.assign(this.form, { kcmc: '', js: '', jc: '', jxcd: '', jhsxs: 0, yxsxs: 0, xf: 0, jh: 0 }) this.$message.warning('后端暂未提供该接口')
this.dialogVisible = true
}, },
handleNewConfirm() { handleNewConfirm() {
if (!this.$refs.formRef) return if (!this.$refs.formRef) return
@@ -349,6 +403,7 @@ export default {
// ==================== 分页 ==================== // ==================== 分页 ====================
handlePageChange(page) { handlePageChange(page) {
this.currentPage = page this.currentPage = page
this.loadList()
}, },
// 表格序号(跨页连续) // 表格序号(跨页连续)
@@ -360,19 +415,6 @@ export default {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
// ==================== 公共区域容器 ====================
.section-card {
background: #fff;
border-radius: 8px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
padding: 16px 20px;
margin-bottom: 16px;
&:last-child {
margin-bottom: 0;
}
}
// ==================== 2. 查询条件区域 ==================== // ==================== 2. 查询条件区域 ====================
.query-card { .query-card {
.query-field { .query-field {
@@ -422,15 +464,6 @@ export default {
} }
} }
// ==================== 3. 导出按钮区域 ====================
.export-card {
.export-bar {
display: flex;
gap: 12px;
flex-wrap: wrap;
}
}
// ==================== 4. 操作按钮区域 ==================== // ==================== 4. 操作按钮区域 ====================
.action-card { .action-card {
.action-row { .action-row {
@@ -485,16 +518,6 @@ export default {
flex-direction: column; flex-direction: column;
line-height: 1.4; line-height: 1.4;
} }
.pagination-wrap {
display: flex;
justify-content: flex-end;
margin-top: 12px;
}
}
.w-full {
width: 100%;
} }
// ==================== 按钮样式 ==================== // ==================== 按钮样式 ====================
@@ -298,7 +298,7 @@
</template> </template>
<script> <script>
import { listSubject, addSubject, updateSubject, deleteSubject, getSubject } from '@/api/teachOffice/subject' import { listKb as listSubject, addKb as addSubject, updateKb as updateSubject, deleteKb as deleteSubject, getKb } from '@/api/teachOffice/kb'
export default { export default {
name: 'SubjectIndex', name: 'SubjectIndex',