学员学籍异动申请

This commit is contained in:
2026-08-23 17:23:17 +08:00
parent c7f334ed53
commit 12214a326f
2 changed files with 438 additions and 182 deletions
@@ -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 }
})
}
+389 -182
View File
@@ -1,178 +1,411 @@
<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 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">{{ statusLabel(row.zt) }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="cjsj" label="创建时间" width="160" show-overflow-tooltip />
<el-table-column prop="xgsj" label="修改时间" width="160" show-overflow-tooltip />
<el-table-column label="操作" width="230" align="center" fixed="right">
<template slot-scope="{ row }">
<el-button type="text" size="small" icon="el-icon-upload2" @click="handleSubmit(row)">提交</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-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
}
},
// ==================== 状态展示 ====================
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 +419,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>