Files
education/frontend/src/views/student/shiftTeam/index.vue
T

718 lines
26 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 shift-team-page">
<!-- ==================== 1. 查询条件 ==================== -->
<el-card shadow="never" class="search-card">
<el-form ref="searchFormRef" :model="searchForm" label-width="90px" 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.xydmc" 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.nj" 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.zydh" 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.xydlx" 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.rwlb" 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.xslx" 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. 工具栏:新增 / 导入 / 导出 ==================== -->
<div class="toolbar">
<el-button type="primary" icon="el-icon-plus" @click="handleAdd">新增学员队</el-button>
<el-button icon="el-icon-download" @click="handleExport">导出 Excel</el-button>
<el-button icon="el-icon-download" @click="handleDownloadTemplate">模板下载</el-button>
<div class="file-input">
<input ref="fileInputRef" type="file" accept=".xls,.xlsx" style="display: none" @change="handleFileChange" />
<el-button icon="el-icon-folder-opened" @click="handleChooseFile">选择文件</el-button>
<span class="file-name">{{ fileName }}</span>
<el-button type="primary" icon="el-icon-upload2" :loading="uploading" @click="handleUpload">上传数据</el-button>
</div>
</div>
<!-- ==================== 3. 列表 ==================== -->
<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="xydbh" label="学员队编号" min-width="120" show-overflow-tooltip />
<el-table-column prop="xydmc" label="学员队名称" min-width="130" show-overflow-tooltip />
<el-table-column prop="nj" label="年级" width="90" align="center" />
<el-table-column prop="zydh" label="专业代号" min-width="110" show-overflow-tooltip />
<el-table-column prop="rwlb" label="任务类别" width="100" align="center" />
<el-table-column label="虚实类型" width="90" align="center">
<template slot-scope="{ row }">
<el-tag :type="Number(row.xslx) === 1 ? 'success' : 'info'" size="mini">
{{ Number(row.xslx) === 1 ? '实' : '虚' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="在校状态" width="90" align="center">
<template slot-scope="{ row }">
<el-tag :type="Number(row.zxzt) === 1 ? 'success' : 'danger'" size="mini">
{{ Number(row.zxzt) === 1 ? '在校' : '毕业' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="xydrs" label="人数" width="80" align="center" />
<el-table-column label="入学日期" width="110" align="center">
<template slot-scope="{ row }">{{ formatDate(row.rxrq) }}</template>
</el-table-column>
<el-table-column label="毕业日期" width="110" align="center">
<template slot-scope="{ row }">{{ formatDate(row.byrq) }}</template>
</el-table-column>
<el-table-column label="操作" width="190" 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 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-wrapper"
@current-change="handlePageChange"
@size-change="handleSizeChange"
/>
</el-card>
<!-- ==================== 4. 新增/编辑对话框 ==================== -->
<el-dialog
:visible="formDialogVisible"
:title="dialogTitle"
width="760px"
:close-on-click-modal="false"
@update:visible="val => formDialogVisible = val"
>
<el-form ref="formRef" :model="form" :rules="formRules" label-width="130px" class="form-dialog-form">
<el-divider content-position="left">基本信息</el-divider>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="学员队编号" prop="xydbh">
<el-input v-model="form.xydbh" placeholder="请输入学员队编号" :disabled="!isAdd" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="学员队名称" prop="xydmc">
<el-input v-model="form.xydmc" placeholder="请输入学员队名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="学员队人数" prop="xydrs">
<el-input-number v-model="form.xydrs" :min="0" controls-position="right" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="年级">
<el-input v-model="form.nj" placeholder="请输入年级" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="专业代号" prop="zydh">
<el-input v-model="form.zydh" placeholder="请输入专业代号" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="专业教室编号">
<el-input v-model="form.zyjsbh" placeholder="请输入专业教室编号" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="学员队类型" prop="xydlx">
<el-input v-model="form.xydlx" placeholder="请输入学员队类型" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="节次类别" prop="jclb">
<el-input v-model="form.jclb" placeholder="请输入节次类别" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="系统模式" prop="xtms">
<el-input v-model="form.xtms" placeholder="请输入系统模式" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="简称">
<el-input v-model="form.jc" placeholder="请输入简称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所属单位">
<el-input v-model="form.ssdw" placeholder="请输入所属单位" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="序号">
<el-input-number v-model="form.xh" :min="0" controls-position="right" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-divider content-position="left">培训与状态</el-divider>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="任务类别" prop="rwlb">
<el-input v-model="form.rwlb" placeholder="请输入任务类别" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="虚实类型" prop="xslx">
<el-select v-model="form.xslx" class="w-full">
<el-option label="虚" :value="0" />
<el-option label="实" :value="1" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="主体培训任务" prop="ztpxrw">
<el-select v-model="form.ztpxrw" class="w-full">
<el-option label="是" :value="1" />
<el-option label="否" :value="0" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="培训任务标识号">
<el-input v-model="form.pxrwbsh" placeholder="请输入培训任务标识号" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="在校状态">
<el-select v-model="form.zxzt" class="w-full">
<el-option label="在校" :value="1" />
<el-option label="毕业" :value="0" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="入学日期" prop="rxrq">
<el-date-picker v-model="form.rxrq" 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="byrq">
<el-date-picker v-model="form.byrq" type="date" value-format="yyyy-MM-dd" placeholder="请选择毕业日期" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="JSON字段">
<el-input v-model="form.jsonzd" placeholder="请输入 JSON 字段" />
</el-form-item>
<el-form-item label="备注">
<el-input v-model="form.bz" type="textarea" :rows="2" placeholder="请输入备注" />
</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>
<!-- ==================== 5. 详情对话框 ==================== -->
<el-dialog title="学员队详情" :visible="detailDialogVisible" width="720px" :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.xydbh || '-' }}</el-descriptions-item>
<el-descriptions-item label="学员队名称">{{ detailForm.xydmc || '-' }}</el-descriptions-item>
<el-descriptions-item label="学员队人数">{{ fmtValue(detailForm.xydrs) }}</el-descriptions-item>
<el-descriptions-item label="年级">{{ detailForm.nj || '-' }}</el-descriptions-item>
<el-descriptions-item label="专业代号">{{ detailForm.zydh || '-' }}</el-descriptions-item>
<el-descriptions-item label="专业教室编号">{{ detailForm.zyjsbh || '-' }}</el-descriptions-item>
<el-descriptions-item label="学员队类型">{{ detailForm.xydlx || '-' }}</el-descriptions-item>
<el-descriptions-item label="节次类别">{{ detailForm.jclb || '-' }}</el-descriptions-item>
<el-descriptions-item label="系统模式">{{ detailForm.xtms || '-' }}</el-descriptions-item>
<el-descriptions-item label="简称">{{ detailForm.jc || '-' }}</el-descriptions-item>
<el-descriptions-item label="所属单位">{{ detailForm.ssdw || '-' }}</el-descriptions-item>
<el-descriptions-item label="序号">{{ fmtValue(detailForm.xh) }}</el-descriptions-item>
<el-descriptions-item label="任务类别">{{ detailForm.rwlb || '-' }}</el-descriptions-item>
<el-descriptions-item label="虚实类型">
<el-tag :type="Number(detailForm.xslx) === 1 ? 'success' : 'info'" size="mini">
{{ Number(detailForm.xslx) === 1 ? '实' : '虚' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="主体培训任务">
<el-tag :type="Number(detailForm.ztpxrw) === 1 ? 'success' : 'info'" size="mini">
{{ Number(detailForm.ztpxrw) === 1 ? '是' : '否' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="培训任务标识号">{{ detailForm.pxrwbsh || '-' }}</el-descriptions-item>
<el-descriptions-item label="在校状态">
<el-tag :type="Number(detailForm.zxzt) === 1 ? 'success' : 'danger'" size="mini">
{{ Number(detailForm.zxzt) === 1 ? '在校' : '毕业' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="入学日期">{{ detailForm.rxrq || '-' }}</el-descriptions-item>
<el-descriptions-item label="毕业日期">{{ detailForm.byrq || '-' }}</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>
import { saveAs } from 'file-saver'
import {
listTeam,
getTeam,
addTeam,
updateTeam,
delTeam,
importTeam,
exportTeam
} from '@/api/studentRecords/team'
export default {
name: 'ShiftTeamIndex',
data() {
return {
// ==================== 查询条件 ====================
searchForm: {
xydmc: '',
nj: '',
zydh: '',
xydlx: '',
rwlb: '',
xslx: undefined
},
// ==================== 文件导入 ====================
selectedFile: null,
uploading: false,
// ==================== 列表数据 ====================
loading: false,
tableData: [],
total: 0,
pageNum: 1,
pageSize: 20,
// ==================== 新增/编辑 ====================
formDialogVisible: false,
dialogTitle: '新增学员队',
isAdd: true,
formSaving: false,
form: this.createEmptyForm(),
formRules: {
xydbh: [{ required: true, message: '请输入学员队编号', trigger: 'blur' }],
xydmc: [{ required: true, message: '请输入学员队名称', trigger: 'blur' }],
xydrs: [{ required: true, message: '请输入学员队人数', trigger: 'change' }],
zydh: [{ required: true, message: '请输入专业代号', trigger: 'blur' }],
rwlb: [{ required: true, message: '请输入任务类别', trigger: 'blur' }],
xslx: [{ required: true, message: '请选择虚实类型', trigger: 'change' }],
rxrq: [{ required: true, message: '请选择入学日期', trigger: 'change' }],
byrq: [{ required: true, message: '请选择毕业日期', trigger: 'change' }],
xydlx: [{ required: true, message: '请输入学员队类型', trigger: 'blur' }],
jclb: [{ required: true, message: '请输入节次类别', trigger: 'blur' }],
xtms: [{ required: true, message: '请输入系统模式', trigger: 'blur' }],
ztpxrw: [{ required: true, message: '请选择主体培训任务', trigger: 'change' }]
},
// ==================== 详情 ====================
detailDialogVisible: false,
detailLoading: false,
detailForm: {}
}
},
computed: {
fileName() {
return (this.selectedFile && this.selectedFile.name) || '未选择任何文件'
}
},
mounted() {
this.fetchList()
},
methods: {
createEmptyForm() {
return {
xydbh: '',
xydmc: '',
xydrs: undefined,
nj: '',
zydh: '',
zyjsbh: '',
bz: '',
zxzt: 1,
xh: undefined,
rwlb: '',
xslx: 1,
rxrq: '',
byrq: '',
xydlx: '',
pxrwbsh: '',
jclb: '',
xtms: '',
jsonzd: '',
jc: '',
ssdw: '',
ztpxrw: 1
}
},
// ==================== 日期/数值展示 ====================
formatDate(val) {
if (!val) return ''
return String(val).substring(0, 10)
},
fmtValue(val) {
return val === null || val === undefined || val === '' ? '-' : val
},
/** 构建查询条件(不含分页),供列表查询与导出共用 */
buildQuery() {
const params = {}
;['xydmc', 'nj', 'zydh', 'xydlx', 'rwlb'].forEach(key => {
const value = this.searchForm[key]
if (value !== '' && value !== null && value !== undefined) {
params[key] = String(value).trim()
}
})
if (this.searchForm.xslx !== undefined && this.searchForm.xslx !== '' && this.searchForm.xslx !== null) {
params.xslx = this.searchForm.xslx
}
return params
},
// ==================== 查询列表 ====================
fetchList() {
this.loading = true
const params = {
pageNum: this.pageNum,
pageSize: this.pageSize,
...this.buildQuery()
}
listTeam(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()
},
// ==================== 新增/编辑 ====================
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()
})
getTeam(row.xydbh).then(response => {
const d = response.data || {}
this.form = {
xydbh: d.xydbh || '',
xydmc: d.xydmc || '',
xydrs: d.xydrs !== null && d.xydrs !== undefined ? d.xydrs : undefined,
nj: d.nj || '',
zydh: d.zydh || '',
zyjsbh: d.zyjsbh || '',
bz: d.bz || '',
zxzt: d.zxzt !== null && d.zxzt !== undefined ? Number(d.zxzt) : 1,
xh: d.xh !== null && d.xh !== undefined ? d.xh : undefined,
rwlb: d.rwlb || '',
xslx: d.xslx !== null && d.xslx !== undefined ? Number(d.xslx) : 1,
rxrq: this.formatDate(d.rxrq),
byrq: this.formatDate(d.byrq),
xydlx: d.xydlx || '',
pxrwbsh: d.pxrwbsh || '',
jclb: d.jclb || '',
xtms: d.xtms || '',
jsonzd: d.jsonzd || '',
jc: d.jc || '',
ssdw: d.ssdw || '',
ztpxrw: d.ztpxrw !== null && d.ztpxrw !== undefined ? Number(d.ztpxrw) : 1
}
}).catch(() => {})
},
buildPayload() {
const f = this.form
const payload = {
xydbh: f.xydbh,
xydmc: f.xydmc,
zydh: f.zydh,
rwlb: f.rwlb,
xydlx: f.xydlx,
jclb: f.jclb,
xtms: f.xtms,
zxzt: Number(f.zxzt),
xslx: Number(f.xslx),
ztpxrw: Number(f.ztpxrw)
}
// 数值字段
if (f.xydrs !== '' && f.xydrs !== null && f.xydrs !== undefined) payload.xydrs = Number(f.xydrs)
if (f.xh !== '' && f.xh !== null && f.xh !== undefined) payload.xh = Number(f.xh)
// 日期字段
if (f.rxrq) payload.rxrq = f.rxrq
if (f.byrq) payload.byrq = f.byrq
// 其余文本字段非空才传
;['nj', 'zyjsbh', 'pxrwbsh', 'jsonzd', 'jc', 'ssdw', 'bz'].forEach(key => {
if (f[key] !== '' && f[key] !== null && f[key] !== undefined) {
payload[key] = String(f[key]).trim()
}
})
return payload
},
handleFormSubmit() {
this.$refs.formRef.validate(valid => {
if (!valid) return
this.formSaving = true
const payload = this.buildPayload()
const request = this.isAdd ? addTeam(payload) : updateTeam(payload)
request.then(() => {
this.$message.success(this.isAdd ? '新增成功' : '修改成功')
this.formDialogVisible = false
this.fetchList()
}).catch(() => {
}).finally(() => {
this.formSaving = false
})
})
},
// ==================== 删除 ====================
handleDelete(row) {
this.$confirm(`确定要删除「${row.xydmc || row.xydbh}」吗?`, '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
return delTeam(row.xydbh)
}).then(() => {
this.$message.success('删除成功')
this.fetchList()
}).catch(() => {})
},
// ==================== 详情 ====================
handleDetail(row) {
this.detailDialogVisible = true
this.detailLoading = true
this.detailForm = {}
getTeam(row.xydbh).then(response => {
this.detailForm = response.data || {}
this.detailLoading = false
}).catch(() => {
this.detailLoading = false
})
},
// ==================== 导出 ====================
handleExport() {
exportTeam(this.buildQuery()).then(blob => {
saveAs(blob, '班次(学员队)管理.xlsx')
this.$message.success('导出成功')
}).catch(() => {})
},
// ==================== 模板下载 / 导入 ====================
handleDownloadTemplate() {
// 后端暂未提供模板下载接口,仅作提示
this.$message.info('后端暂未提供该接口')
},
handleChooseFile() {
this.$refs.fileInputRef && this.$refs.fileInputRef.click()
},
handleFileChange(e) {
const input = e.target
this.selectedFile = (input.files && input.files[0]) || null
},
handleUpload() {
if (!this.selectedFile) {
this.$message.warning('请先选择文件')
return
}
this.uploading = true
importTeam(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
})
}
}
}
</script>
<style scoped lang="scss">
.app-container {
padding: 20px;
}
.shift-team-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;
}
}
}
.toolbar {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
margin-bottom: 16px;
.file-input {
display: flex;
align-items: center;
gap: 8px;
.file-name {
font-size: 12px;
color: #909399;
max-width: 180px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
.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;
}
}
.form-dialog-form {
max-height: 62vh;
overflow-y: auto;
padding-right: 6px;
::v-deep .el-divider--horizontal {
margin: 4px 0 16px;
}
}
.text-danger {
color: #f56c6c;
padding: 0;
}
}
</style>