Files
education/frontend/src/views/teachOffice/taskPlan/index.vue
T

514 lines
17 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="app-container task-plan-page">
<!-- ==================== 页面标题 ==================== -->
<div class="page-title">教学任务计划管理</div>
<!-- ==================== 1. 查询条件区域 ==================== -->
<el-card shadow="never" class="search-card">
<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-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-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-toolbar">
<div class="left-group">
<el-button type="primary" icon="el-icon-plus" @click="handleAdd">新建教学任务</el-button>
</div>
</div>
<!-- ==================== 3. 数据表格 ==================== -->
<el-card shadow="never" class="table-card">
<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="任务名称" width="250" align="left" header-align="center" show-overflow-tooltip />
<el-table-column prop="nd" label="年度" width="150" align="center" />
<el-table-column label="状态" width="100" align="center">
<template slot-scope="{ row }">
<el-tag :type="row.zt === '发布' ? 'success' : 'info'" size="small">
{{ row.zt || '-' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="fbsj" label="发布时间" width="180" align="center" :formatter="fmtDateTime" />
<el-table-column prop="jssj" label="结束时间" width="180" align="center" :formatter="fmtDateTime" />
<el-table-column prop="cjsj" label="创建时间" width="180" align="center" :formatter="fmtDateTime" />
<el-table-column prop="jcxqscsj" label="教材需求生成时间" align="center" :formatter="fmtDateTime" />
<el-table-column label="操作" width="300" 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"
: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>
<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="dialogTitle"
width="560px"
:close-on-click-modal="false"
@update:visible="val => dialogVisible = val"
>
<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>
<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="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 @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. 查询条件 ====================
searchForm: {
rwmc: '',
nd: undefined,
zt: ''
},
yearOptions: [],
statusOptions: [
{ label: '未发布', value: '未发布' },
{ label: '发布', value: '发布' }
],
// ==================== 2. 表格数据 ====================
loading: false,
tableData: [],
pageNum: 1,
pageSize: 10,
total: 0,
// ==================== 3. 新增/修改表单 ====================
dialogVisible: false,
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' }]
},
// ==================== 4. 详情 ====================
detailVisible: false,
detailLoading: false,
detailData: {}
}
},
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 []
})
},
/* ---------- 通用格式化 ---------- */
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
},
/* ---------- 列表加载 ---------- */
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: ''
}
},
handleAdd() {
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(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()
})
},
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
})
})
},
/** 构建请求体:年度转数字以匹配后端 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(() => deleteTeachingTask(row.bh)).then(() => {
this.$message.success('删除成功')
this.fetchList()
}).catch(() => {})
},
/* ---------- 发布 ---------- */
handlePublish(row) {
this.$confirm(`确定要发布教学任务「${row.rwmc || row.bh || '该记录'}」吗?发布前请先填写关联的教研室任务书。`, '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => publishTeachingTask(row.bh)).then(() => {
this.$message.success('发布成功')
this.fetchList()
}).catch(() => {})
}
}
}
</script>
<style scoped lang="scss">
.app-container {
padding: 20px;
}
.task-plan-page {
.page-title {
text-align: center;
font-size: 20px;
font-weight: 600;
color: #303133;
margin-bottom: 20px;
}
// ========== 1. 查询条件区域 ==========
.search-card {
margin-bottom: 16px;
.search-form {
.el-form-item {
margin-bottom: 0;
margin-right: 24px;
}
}
}
// ========== 2. 工具栏 ==========
.list-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 12px;
margin-bottom: 16px;
.left-group,
.right-group {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 10px;
}
}
// ========== 3. 数据表格区域 ==========
.table-card {
.task-table {
width: 100%;
}
.pagination {
display: flex;
justify-content: flex-end;
margin-top: 12px;
}
.text-danger {
color: #f56c6c;
}
}
// ========== 4. 详情 ==========
.detail-empty {
min-height: 120px;
display: flex;
align-items: center;
justify-content: center;
color: #909399;
}
}
</style>