Files
railwaysystem/build_ui/src/views/train/accommodation/index.vue
T
wangzhibiaoandCursor aeb76ce35a 修复住宿:统一住宿生命周期状态与状态变更入口
住宿状态在数据库与业务上只有 0 预分配 / 1 已入住 / 2 已退宿三种,不存在 3 已调房。
调房是业务操作,真实语义为旧记录置退宿、新记录置在住,不应表现为独立状态值。

- 前端住宿页面移除筛选项与两处状态标签中的"已调房"(status=3),补齐历史表格缺失的预分配标签
- 后端通用 PUT /train/accommodation 封死 accommodation_status 写入路径,
  状态变更只能经办理入住、办理退宿、调房等专属业务操作
- 新增住宿状态白名单守卫,非法整数与新增即退宿一律拒绝入库

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 17:03:43 +08:00

331 lines
13 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">
<el-form v-show="showSearch" ref="queryForm" :model="queryParams" size="small" :inline="true" label-width="72px">
<el-form-item label="学员学号" prop="studentId">
<el-input
v-model="queryParams.studentId"
placeholder="请输入学号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="宿舍楼栋" prop="dormBuilding">
<el-input
v-model="queryParams.dormBuilding"
placeholder="请输入楼栋"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="房间号" prop="dormRoom">
<el-input
v-model="queryParams.dormRoom"
placeholder="请输入房间号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="住宿状态" prop="accommodationStatus">
<el-select v-model="queryParams.accommodationStatus" placeholder="全部状态" clearable>
<el-option label="预分配" :value="0" />
<el-option label="在住" :value="1" />
<el-option label="已退宿" :value="2" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
v-hasPermi="['train:accommodation:add']"
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
>分配住宿</el-button>
</el-col>
<right-toolbar :show-search.sync="showSearch" @queryTable="getList" />
</el-row>
<el-table v-loading="loading" :data="accommodationList">
<el-table-column label="住宿ID" align="center" prop="accommodationId" width="80" />
<el-table-column label="报名编号" align="center" prop="enrollmentId" width="90" />
<el-table-column label="学员姓名" align="center" width="110">
<template slot-scope="scope">
<span>{{ scope.row.studentName || scope.row.studentId || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="学员学号" align="center" prop="studentId" width="120" />
<el-table-column label="宿舍楼栋" align="center" prop="dormBuilding" width="100" />
<el-table-column label="房间号" align="center" prop="dormRoom" width="90" />
<el-table-column label="床位号" align="center" prop="bedNo" width="80" />
<el-table-column label="住宿状态" align="center" width="120">
<!-- 住宿状态仅有 0=预分配 / 1=在住 / 2=已退宿 三种;调房由「旧记录置退宿 + 新记录置在住」表达,没有独立状态值 -->
<template slot-scope="scope">
<el-tag v-if="scope.row.accommodationStatus === 1" type="success" size="small">在住</el-tag>
<el-tag v-else-if="scope.row.accommodationStatus === 0" type="info" size="small">预分配</el-tag>
<el-tag v-else-if="scope.row.accommodationStatus === 2" type="danger" size="small">已退宿</el-tag>
<el-tag v-else size="small">{{ scope.row.accommodationStatus }}</el-tag>
</template>
</el-table-column>
<el-table-column label="入住时间" align="center" prop="checkInTime" width="150">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.checkInTime, '{y}-{m}-{d} {h}:{i}') }}</span>
</template>
</el-table-column>
<el-table-column label="退宿时间" align="center" prop="checkOutTime" width="150">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.checkOutTime, '{y}-{m}-{d} {h}:{i}') }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" min-width="180">
<template slot-scope="scope">
<el-button
v-if="scope.row.accommodationStatus === 0"
v-hasPermi="['train:accommodation:edit']"
size="mini"
type="text"
icon="el-icon-check"
@click="handleCheckIn(scope.row)"
>入住</el-button>
<el-button
v-if="scope.row.accommodationStatus === 0"
v-hasPermi="['train:accommodation:remove']"
size="mini"
type="text"
icon="el-icon-delete"
style="color: #F56C6C;"
@click="handleDelete(scope.row)"
>删除</el-button>
<el-button
v-if="scope.row.accommodationStatus === 1"
v-hasPermi="['train:accommodation:edit']"
size="mini"
type="text"
icon="el-icon-sort"
@click="handleOpenTransfer(scope.row)"
>调房</el-button>
<el-button
v-if="scope.row.accommodationStatus === 1"
v-hasPermi="['train:accommodation:edit']"
size="mini"
type="text"
icon="el-icon-close"
style="color: #F56C6C;"
@click="handleCheckOut(scope.row)"
>退宿</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-document"
@click="handleViewHistory(scope.row)"
>住宿履历</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 办理调房对话框 -->
<el-dialog title="办理学员调房 (Room Transfer)" :visible.sync="transferOpen" width="500px" append-to-body>
<el-form ref="transferForm" :model="transferData" :rules="transferRules" label-width="100px">
<el-form-item label="当前学员">
<span>{{ currentRecord.studentName || currentRecord.studentId }} ({{ currentRecord.dormBuilding }} {{ currentRecord.dormRoom }} {{ currentRecord.bedNo }}床)</span>
</el-form-item>
<el-form-item label="目标宿舍" prop="newDormId">
<el-select v-model="transferData.newDormId" placeholder="请选择目标宿舍" filterable style="width: 100%;">
<el-option
v-for="d in dormOptions"
:key="d.dormId"
:label="d.dormBuilding + ' ' + d.dormRoom + ' (余位:' + (d.remainingBeds != null ? d.remainingBeds : d.capacity) + ')'"
:value="d.dormId"
/>
</el-select>
</el-form-item>
<el-form-item label="新床位号" prop="newBedNo">
<el-input v-model="transferData.newBedNo" placeholder="请输入新床位号,如 1号床" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" id="btnSubmitTransfer" @click="submitTransfer">确认调房</el-button>
<el-button @click="transferOpen = false">取 消</el-button>
</div>
</el-dialog>
<!-- 历史住宿履历对话框 -->
<el-dialog title="学员历史住宿记录" :visible.sync="historyOpen" width="750px" append-to-body>
<el-alert
title="住宿说明:系统按班级参训周期管理住宿,同一学员同一时间仅保留一条在住记录。"
type="info"
:closable="false"
show-icon
style="margin-bottom: 15px;"
/>
<el-table v-loading="historyLoading" :data="historyList" size="small" border empty-text="暂无历史住宿记录">
<el-table-column label="记录ID" prop="accommodationId" width="70" align="center" />
<el-table-column label="宿舍房间" align="center" min-width="130">
<template slot-scope="scope">
<span>{{ scope.row.dormBuilding }} {{ scope.row.dormRoom }} ({{ scope.row.bedNo }}床)</span>
</template>
</el-table-column>
<el-table-column label="住宿状态" align="center" width="110">
<template slot-scope="scope">
<el-tag v-if="scope.row.accommodationStatus === 1" type="success" size="mini">在住</el-tag>
<el-tag v-else-if="scope.row.accommodationStatus === 0" type="info" size="mini">预分配</el-tag>
<el-tag v-else-if="scope.row.accommodationStatus === 2" type="danger" size="mini">已退宿</el-tag>
<el-tag v-else size="mini">{{ scope.row.accommodationStatus }}</el-tag>
</template>
</el-table-column>
<el-table-column label="入住时间" align="center" width="150">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.checkInTime, '{y}-{m}-{d} {h}:{i}') }}</span>
</template>
</el-table-column>
<el-table-column label="退宿/调离时间" align="center" width="150">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.checkOutTime, '{y}-{m}-{d} {h}:{i}') }}</span>
</template>
</el-table-column>
</el-table>
<div slot="footer" class="dialog-footer">
<el-button @click="historyOpen = false">关 闭</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listAccommodation, checkInAccommodation, checkOutAccommodation, transferAccommodation, getAccommodationHistory, delAccommodation } from '@/api/train/accommodation'
import { listDormitory } from '@/api/train/dormitory'
export default {
name: 'Accommodation',
data() {
return {
loading: true,
showSearch: true,
total: 0,
accommodationList: [],
queryParams: {
pageNum: 1,
pageSize: 10,
studentId: null,
dormBuilding: null,
dormRoom: null,
accommodationStatus: null
},
currentRecord: {},
transferOpen: false,
transferData: {
newDormId: null,
newBedNo: '1号床'
},
transferRules: {
newDormId: [{ required: true, message: '请选择目标宿舍', trigger: 'change' }],
newBedNo: [{ required: true, message: '请输入新床位号', trigger: 'blur' }]
},
dormOptions: [],
historyOpen: false,
historyLoading: false,
historyList: []
}
},
created() {
this.getList()
this.loadDorms()
},
methods: {
getList() {
this.loading = true
listAccommodation(this.queryParams).then(response => {
this.accommodationList = response.rows || []
this.total = response.total || 0
this.loading = false
}).catch(() => {
this.loading = false
})
},
loadDorms() {
listDormitory({ pageNum: 1, pageSize: 50 }).then(res => {
this.dormOptions = res.rows || []
})
},
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
resetQuery() {
this.resetForm('queryForm')
this.handleQuery()
},
handleAdd() {
this.$modal.msgInfo('请在学员管理或报名名单中点击“住宿安排”办理分配')
},
handleCheckIn(row) {
this.$modal.confirm('确认办理学员 [' + (row.studentName || row.studentId) + '] 的入住手续?').then(() => {
return checkInAccommodation(row.accommodationId)
}).then(() => {
this.$modal.msgSuccess('入住办理成功')
this.getList()
}).catch(() => {})
},
handleCheckOut(row) {
this.$modal.confirm('确认办理学员 [' + (row.studentName || row.studentId) + '] 的退宿手续?').then(() => {
return checkOutAccommodation(row.accommodationId)
}).then(() => {
this.$modal.msgSuccess('退宿办理成功')
this.getList()
}).catch(() => {})
},
handleOpenTransfer(row) {
this.currentRecord = row
this.transferData.newDormId = null
this.transferData.newBedNo = '2号床'
this.transferOpen = true
},
submitTransfer() {
this.$refs.transferForm.validate(valid => {
if (valid) {
transferAccommodation(this.currentRecord.accommodationId, this.transferData.newDormId, this.transferData.newBedNo).then(() => {
this.$modal.msgSuccess('调房办理成功')
this.transferOpen = false
this.getList()
})
}
})
},
handleViewHistory(row) {
this.historyOpen = true
this.historyLoading = true
getAccommodationHistory(row.enrollmentId).then(res => {
this.historyList = res.data || []
this.historyLoading = false
}).catch(() => {
this.historyLoading = false
})
},
handleDelete(row) {
const studentDesc = row.studentName ? `${row.studentName} (${row.studentId || ''})` : (row.studentId || `ID:${row.accommodationId}`)
const dormDesc = `${row.dormBuilding || ''} ${row.dormRoom || ''}`
this.$modal.confirm(`确定要删除学员【${studentDesc}】在宿舍【${dormDesc}】的预分配住宿记录吗?`).then(() => {
return delAccommodation(row.accommodationId)
}).then(() => {
this.$modal.msgSuccess('删除成功')
this.getList()
}).catch(() => {})
}
}
}
</script>