Files
education/frontend/src/views/teachingLog/organLog/components/logView.vue
T
2026-08-28 20:31:41 +08:00

568 lines
20 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="page-container">
<!-- 查询条件 -->
<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-item label="开始日期">
<el-date-picker v-model="searchForm.ksrq" type="date" value-format="yyyy-MM-dd" placeholder="选择开始日期"
class="w-full" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="结束日期">
<el-date-picker v-model="searchForm.jsrq" type="date" value-format="yyyy-MM-dd" placeholder="选择结束日期"
class="w-full" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="0">
<el-col :span="12">
<el-form-item label="课程名称">
<el-input v-model="searchForm.kcmc" placeholder="请输入课程名称" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="教研室">
<el-select v-model="searchForm.jys" placeholder="请选择教研室" clearable class="w-full">
<el-option v-for="item in jysOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="0">
<el-col :span="12">
<el-form-item label="教学方法">
<el-select v-model="searchForm.jxff" placeholder="请选择教学方法" clearable class="w-full">
<el-option v-for="item in jxffOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="日志状态">
<el-select v-model="searchForm.rzzt" placeholder="请选择日志状态" clearable class="w-full">
<el-option v-for="item in rzztOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="0">
<el-col :span="12">
<el-form-item label="授课教员">
<el-select v-model="searchForm.skjy" placeholder="请选择授课教员" clearable class="w-full">
<el-option v-for="item in skjyOptions" :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="searchForm.pxqb" placeholder="请输入培训期班" clearable />
</el-form-item>
</el-col>
</el-row>
<!-- <el-row :gutter="0">
<el-col :span="12">
<el-form-item label="改动情况">
<div class="change-options">
<el-radio-group v-model="searchForm.changeType">
<el-radio v-for="item in changeTypeOptions" :key="item.value" :label="item.value">{{ item.label
}}</el-radio>
</el-radio-group>
<el-checkbox-group v-model="searchForm.changeTypes">
<el-checkbox v-for="item in changeTypeList" :key="item.value" :label="item.value">{{ item.label
}}</el-checkbox>
</el-checkbox-group>
</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="任务类别">
<el-select v-model="searchForm.rwlb" placeholder="请选择任务类别" clearable class="w-full">
<el-option v-for="item in rwlbOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
</el-col>
</el-row> -->
</el-form>
<div class="search-actions">
<div class="left-group">
<el-button type="primary" @click="handleBatchReport">批准上报所选</el-button>
<!-- <el-button @click="handleRecheck">重新检查通报变更情况</el-button> -->
<el-button icon="el-icon-download" @click="handleExport">导出</el-button>
<el-button icon="el-icon-refresh" @click="handleRefresh">刷新</el-button>
<el-button icon="el-icon-upload2" :loading="importing" @click="handleImportExcel">导入教学日志</el-button>
<el-button icon="el-icon-download" @click="handleDownloadTemplate">下载模板</el-button>
</div>
<el-button type="primary" @click="handleSearch">查询</el-button>
</div>
<!-- 隐藏的文件选择器用于导入 Excel -->
<input ref="importFileInput" type="file" accept=".xls,.xlsx" style="display: none" @change="handleImportFileChange" />
</el-card>
<!-- 数据表格 -->
<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="50" align="center" />
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="rwlb" label="库任务类别" width="120" align="center" />
<el-table-column prop="jyxm" label="主讲教员" width="100" align="center" />
<el-table-column prop="rq" label="日期" width="150" align="center">
<template slot-scope="scope">{{ scope.row.rq ? formatDate(scope.row.rq) : '-' }}</template>
</el-table-column>
<el-table-column label="节次" width="80" align="center">
<template slot-scope="scope">
{{ scope.row.qsjc && scope.row.jsjc ? `${scope.row.qsjc}-${scope.row.jsjc}` : (scope.row.qsjc || '-') }}
</template>
</el-table-column>
<el-table-column prop="kcmc" label="课程名称" min-width="150" show-overflow-tooltip />
<el-table-column prop="pxqbxx" label="培训期班信息" min-width="140" show-overflow-tooltip />
<el-table-column label="人数" width="70" align="center">
<template slot-scope="scope">{{ scope.row.sdrs || scope.row.ydrs || '-' }}</template>
</el-table-column>
<el-table-column label="分组指导教员" width="120" align="center">
<template slot-scope="scope">{{ scope.row.fzyzdjy || '-' }}</template>
</el-table-column>
<el-table-column prop="jsfs" label="教学方法" width="100" align="center" />
<el-table-column prop="zt" label="日志状态" width="100" align="center" />
<el-table-column prop="gdqk" label="变动汇总" min-width="120" show-overflow-tooltip />
<el-table-column prop="jxrzcjfs" label="创建方式" width="100" align="center" />
<el-table-column label="操作" width="80" align="center" fixed="right" class-name="table-action-column">
<template slot-scope="scope">
<el-button type="text" @click="handleReport(scope.row)">上报</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-wrapper">
<el-pagination :current-page="pageNum" :page-size="pageSize" :total="total" :page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper" background @current-change="handlePageChange"
@size-change="handleSizeChange" />
</div>
</el-card>
<!-- 日志详情对话框 -->
<el-dialog :visible.sync="detailVisible" title="教学日志详情" width="700px" destroy-on-close>
<div v-loading="detailLoading">
<el-descriptions v-if="!detailLoading" :column="2" border>
<el-descriptions-item label="编号">{{ detailData.bh || '-' }}</el-descriptions-item>
<el-descriptions-item label="统一编号">{{ detailData.tybh || '-' }}</el-descriptions-item>
<el-descriptions-item label="教员编号">{{ detailData.jybh || '-' }}</el-descriptions-item>
<el-descriptions-item label="教员姓名">{{ detailData.jyxm || '-' }}</el-descriptions-item>
<el-descriptions-item label="课程名称">{{ detailData.kcmc || '-' }}</el-descriptions-item>
<el-descriptions-item label="任务类别">{{ detailData.rwlb || '-' }}</el-descriptions-item>
<el-descriptions-item label="日期">{{ detailData.rq || '-' }}</el-descriptions-item>
<el-descriptions-item label="节次">
{{ detailData.qsjc && detailData.jsjc ? `${detailData.qsjc}-${detailData.jsjc}` : '-' }}
</el-descriptions-item>
<el-descriptions-item label="教学方法">{{ detailData.jsfs || detailData.jxff || '-' }}</el-descriptions-item>
<el-descriptions-item label="创建方式">{{ detailData.jxrzcjfs || '-' }}</el-descriptions-item>
<el-descriptions-item label="应到人数">{{ detailData.ydrs || '-' }}</el-descriptions-item>
<el-descriptions-item label="实到人数">{{ detailData.sdrs || '-' }}</el-descriptions-item>
<el-descriptions-item label="培训期班信息" :span="2">{{ detailData.pxqbxx || '-' }}</el-descriptions-item>
<el-descriptions-item label="授课地点" :span="2">{{ detailData.skdd || '-' }}</el-descriptions-item>
<el-descriptions-item label="状态">{{ detailData.zt || '-' }}</el-descriptions-item>
<el-descriptions-item label="教研室">{{ detailData.jysmc || detailData.jys || '-' }}</el-descriptions-item>
<el-descriptions-item label="变动汇总" :span="2">{{ detailData.gdqk || '-' }}</el-descriptions-item>
<el-descriptions-item label="变动详细情况" :span="2">{{ detailData.gdxxqk || '-' }}</el-descriptions-item>
<el-descriptions-item label="退回意见" :span="2">{{ detailData.thyj || '-' }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ detailData.cjsj || '-' }}</el-descriptions-item>
<el-descriptions-item label="修改时间">{{ detailData.xgsj || '-' }}</el-descriptions-item>
</el-descriptions>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="detailVisible = false"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { formatDate } from '@/utils/index'
import {
getTeachingLogList,
exportTeachingLog,
importTeachingLogExcel,
downloadTeachingLogTemplate,
getTeachingLogByBh,
batchReportTeachingLog
} from '@/api/log'
export default {
name: 'LogView',
data() {
return {
// ==================== 查询表单 ====================
searchForm: {
ksrq: '',
jsrq: '',
kcmc: '',
jys: '',
jxff: '',
rzzt: '',
skjy: '',
pxqb: '',
changeType: '',
changeTypes: [],
rwlb: ''
},
jysOptions: ['教研室', '防空兵系', '通信工程系', '装甲兵系'],
jxffOptions: ['理论讲授', '讨论', '实操', '演示', '实验', '演习'],
skjyOptions: ['教务处', '张三', '李四', '王五', '赵六'],
rzztOptions: ['教务部门导入教员类', '管理员手工添加'],
rwlbOptions: ['全军院校训练规划内任务', '其他任务'],
changeTypeOptions: [
{ label: '与教学计划一致', value: '与教学计划一致' },
{ label: '已改动教学计划', value: '已改动教学计划' },
{ label: '手动拟制', value: '手动拟制' }
],
changeTypeList: [
{ label: '已改动教员', value: '已改动教员' },
{ label: '已改动时间', value: '已改动时间' },
{ label: '已改动课目', value: '已改动课目' },
{ label: '已改动内容', value: '已改动内容' },
{ label: '已改动教学方法', value: '已改动教学方法' },
{ label: '已改动场地', value: '已改动场地' },
{ label: '已改动期班', value: '已改动期班' }
],
// ==================== 数据表格 ====================
loading: false,
tableData: [],
selectedRows: [],
pageNum: 1,
pageSize: 20,
total: 0,
// ==================== 导入 ====================
importing: false,
// 导入成功后是否跳到最后一页(定位到刚导入的数据)
importJumpLast: false,
// ==================== 详情 ====================
detailVisible: false,
detailLoading: false,
detailData: {}
}
},
created() {
this.fetchList()
},
methods: {
formatDate(val) {
return formatDate(val)
},
handleSelectionChange(rows) {
this.selectedRows = rows
},
/** 根据已填写的查询值构建实际查询参数 */
buildSearchParams() {
const form = this.searchForm
const params = { pageNum: this.pageNum, pageSize: this.pageSize }
if (form.ksrq) params.ksrq = form.ksrq
if (form.jsrq) params.jsrq = form.jsrq
if (form.kcmc) params.kcmc = form.kcmc
if (form.jys) params.jys = form.jys
if (form.jxff) params.jxff = form.jxff
if (form.rzzt) params.rzzt = form.rzzt
if (form.skjy) params.skjy = form.skjy
if (form.pxqb) params.pxqb = form.pxqb
if (form.rwlb) params.rwlb = form.rwlb
if (form.changeType) params.gdqk = form.changeType
if (form.changeTypes.length > 0) {
params.gdxxqk = form.changeTypes.join(',')
}
return params
},
/** 从响应中提取列表数据和总数(兼容 records / list / rows 等常见字段名) */
extractListData(res) {
const data = res.data || res
const list = data.records || data.list || data.rows || []
const totalCount = data.total ?? data.totalCount ?? data.totalRows ?? 0
return { list, total: totalCount }
},
fetchList() {
this.loading = true
return getTeachingLogList(this.buildSearchParams())
.then((res) => {
// 列表接口返回裸 PageResult(无 code 包装),拦截器已保证成功,直接取数据
const { list, total } = this.extractListData(res)
// 导入成功后:跳到最后一页,定位刚导入的数据
if (this.importJumpLast) {
this.importJumpLast = false
const lastPage = total > 0 ? Math.ceil(total / this.pageSize) : 1
if (lastPage !== this.pageNum) {
this.pageNum = lastPage
this.fetchList()
return
}
}
this.tableData = list
this.total = total
})
.catch(() => {
// 错误已由拦截器统一处理
})
.finally(() => {
this.loading = false
})
},
handleSearch() {
this.pageNum = 1
this.fetchList()
},
handlePageChange(page) {
this.pageNum = page
this.fetchList()
},
handleSizeChange(size) {
this.pageSize = size
this.pageNum = 1
this.fetchList()
},
handleRefresh() {
this.fetchList()
},
getSelectedBhs() {
const ids = this.selectedRows.map((row) => row.bh).filter(Boolean)
if (ids.length === 0) {
this.$message.warning('请先选择要操作的记录')
return []
}
return ids
},
handleBatchReport() {
const ids = this.getSelectedBhs()
if (ids.length === 0) return
this.$confirm(`确定要上报选中的 ${ids.length} 条记录吗?`, '上报确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
// 批量上报教学日志
return batchReportTeachingLog(ids)
})
.then(() => {
this.$message.success(`已上报 ${ids.length} 条记录`)
this.fetchList()
})
.catch(() => {})
},
handleReport(row) {
const bh = row && row.bh
if (!bh) {
this.$message.warning('记录编号不存在,无法上报')
return
}
this.$confirm('确定要上报该条教学日志吗?', '上报确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => batchReportTeachingLog([String(bh)]))
.then(() => {
this.$message.success('上报成功')
this.fetchList()
})
.catch(() => {})
},
handleRecheck() {
// 后端暂未提供重新检查接口,仅作提示
this.$message.info('后端暂未提供该接口')
},
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)
},
handleExport() {
const params = this.buildSearchParams()
delete params.pageNum
delete params.pageSize
exportTeachingLog(params)
.then((blob) => {
this.downloadBlob(blob, `教学日志_${new Date().toISOString().slice(0, 10)}.xls`)
this.$message.success('导出成功')
})
.catch(() => {})
},
handleImportExcel() {
this.$refs.importFileInput && this.$refs.importFileInput.click()
},
handleImportFileChange(e) {
const files = e.target.files
if (!files || files.length === 0) return
const file = files[0]
this.importing = true
importTeachingLogExcel(file)
.then((res) => {
if (res.code === 200 || res.code === 0) {
this.$message.success(res.message || '导入成功')
this.importJumpLast = true
this.fetchList()
} else {
this.$message.error(res.message || '导入失败')
}
})
.catch(() => {})
.finally(() => {
this.importing = false
if (this.$refs.importFileInput) {
this.$refs.importFileInput.value = ''
}
})
},
handleDownloadTemplate() {
downloadTeachingLogTemplate()
.then((blob) => {
this.downloadBlob(blob, '导入教学日志模板.xls')
this.$message.success('模板下载成功')
})
.catch(() => {})
},
handleDetail(row) {
this.detailData = { ...row }
this.detailVisible = true
const bh = row.bh
if (!bh) return
this.detailLoading = true
getTeachingLogByBh(String(bh))
.then((res) => {
if ((res.code === 200 || res.code === 0) && res.data) {
this.detailData = res.data
}
})
.catch(() => {
// 详情接口不可用时保留行数据兜底展示
})
.finally(() => {
this.detailLoading = false
})
}
}
}
</script>
<style scoped lang="scss">
.page-container {
padding: 4px 8px;
}
.search-card {
margin-bottom: 16px;
.search-form {
.w-full {
width: 100%;
}
::v-deep .el-row {
border-bottom: 1px solid #ebeef5;
&:last-child {
border-bottom: none;
}
}
::v-deep .el-col {
border-right: 1px solid #ebeef5;
&:last-child {
border-right: none;
}
}
::v-deep .el-form-item__label {
white-space: normal;
line-height: 1.4;
text-align: left;
justify-content: flex-start;
}
.el-form-item {
margin-bottom: 0;
padding: 10px 12px;
}
.change-options {
display: flex;
flex-direction: column;
gap: 8px;
::v-deep .el-radio-group,
::v-deep .el-checkbox-group {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
}
}
.search-tip {
padding: 6px 12px 0;
font-size: 12px;
color: #f56c6c;
}
.search-actions {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px;
border-top: 1px solid #ebeef5;
.left-group {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
}
}
.table-card {
.el-table {
width: 100%;
}
.pagination-wrapper {
display: flex;
justify-content: flex-end;
padding: 16px 0 0;
}
}
</style>