feat(sprint12): 床位管理模块 — 全栈CRUD+状态流转+Flyway迁移

- 后端: Bed Entity/Mapper/Service/Controller (6个文件)
- Flyway: V12__bed_management.sql (sys_bed表+索引)
- 前端: bedspace页面完整CRUD (搜索/表格/新增编辑弹窗/状态流转/分页)
- 状态: 空闲(0)→占用(1)→清洁(2)→空闲(0), 维修(3)独立
- 编译: BUILD SUCCESS
This commit is contained in:
2026-06-06 11:31:55 +08:00
parent 7553c711b2
commit b632dedcd0
8 changed files with 211 additions and 23 deletions

View File

@@ -0,0 +1,55 @@
package com.healthlink.his.web.basicmanage.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.core.domain.R;
import com.healthlink.his.basicmanage.domain.Bed;
import com.healthlink.his.basicmanage.service.IBedService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/basic-manage/bed")
@Slf4j
@AllArgsConstructor
public class BedController {
private final IBedService bedService;
@GetMapping("/page")
public R<?> getPage(@RequestParam(value = "bedNo", required = false) String bedNo,
@RequestParam(value = "wardId", required = false) Long wardId,
@RequestParam(value = "status", required = false) Integer status,
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(value = "pageSize", defaultValue = "20") Integer pageSize) {
LambdaQueryWrapper<Bed> wrapper = new LambdaQueryWrapper<>();
wrapper.like(StringUtils.hasText(bedNo), Bed::getBedNo, bedNo)
.eq(wardId != null, Bed::getWardId, wardId)
.eq(status != null, Bed::getStatus, status)
.orderByDesc(Bed::getCreateTime);
return R.ok(bedService.page(new Page<>(pageNo, pageSize), wrapper));
}
@GetMapping("/list")
public R<List<Bed>> getList(@RequestParam(value = "status", required = false) Integer status) {
LambdaQueryWrapper<Bed> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(status != null, Bed::getStatus, status).orderByAsc(Bed::getBedNo);
return R.ok(bedService.list(wrapper));
}
@PostMapping("/add")
public R<?> add(@RequestBody Bed bed) {
return bedService.save(bed) ? R.ok("新增成功") : R.fail("新增失败");
}
@PutMapping("/update")
public R<?> update(@RequestBody Bed bed) {
return bedService.updateById(bed) ? R.ok("修改成功") : R.fail("修改失败");
}
@DeleteMapping("/delete")
public R<?> delete(@RequestParam Long id) {
return bedService.removeById(id) ? R.ok("删除成功") : R.fail("删除失败");
}
@PutMapping("/status")
public R<?> updateStatus(@RequestParam Long id, @RequestParam Integer status) {
Bed bed = new Bed(); bed.setId(id); bed.setStatus(status);
return bedService.updateById(bed) ? R.ok("状态更新成功") : R.fail("状态更新失败");
}
}

View File

@@ -0,0 +1,20 @@
CREATE TABLE IF NOT EXISTS sys_bed (
id BIGSERIAL PRIMARY KEY,
bed_no VARCHAR(20) NOT NULL,
bed_name VARCHAR(50),
ward_id BIGINT,
ward_name VARCHAR(100),
dept_id BIGINT,
dept_name VARCHAR(100),
bed_type INT DEFAULT 1,
status INT DEFAULT 0,
remark TEXT,
create_by VARCHAR(64),
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_by VARCHAR(64),
update_time TIMESTAMP,
tenant_id INT DEFAULT 0
);
CREATE INDEX IF NOT EXISTS idx_bed_ward ON sys_bed(ward_id);
CREATE INDEX IF NOT EXISTS idx_bed_dept ON sys_bed(dept_id);
CREATE INDEX IF NOT EXISTS idx_bed_status ON sys_bed(status);

View File

@@ -0,0 +1,23 @@
package com.healthlink.his.basicmanage.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.core.common.core.domain.HisBaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@TableName("sys_bed")
@EqualsAndHashCode(callSuper = false)
public class Bed extends HisBaseEntity {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
private String bedNo;
private String bedName;
private Long wardId;
private String wardName;
private Long deptId;
private String deptName;
private Integer bedType;
private Integer status;
private String remark;
}

View File

@@ -0,0 +1,6 @@
package com.healthlink.his.basicmanage.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.healthlink.his.basicmanage.domain.Bed;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface BedMapper extends BaseMapper<Bed> {}

View File

@@ -0,0 +1,4 @@
package com.healthlink.his.basicmanage.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.healthlink.his.basicmanage.domain.Bed;
public interface IBedService extends IService<Bed> {}

View File

@@ -0,0 +1,8 @@
package com.healthlink.his.basicmanage.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.basicmanage.domain.Bed;
import com.healthlink.his.basicmanage.mapper.BedMapper;
import com.healthlink.his.basicmanage.service.IBedService;
import org.springframework.stereotype.Service;
@Service
public class BedServiceImpl extends ServiceImpl<BedMapper, Bed> implements IBedService {}

View File

@@ -0,0 +1,6 @@
import request from '@/utils/request'
export function getBedPage(params) { return request({ url: '/basic-manage/bed/page', method: 'get', params }) }
export function addBed(data) { return request({ url: '/basic-manage/bed/add', method: 'post', data }) }
export function updateBed(data) { return request({ url: '/basic-manage/bed/update', method: 'put', data }) }
export function deleteBed(id) { return request({ url: '/basic-manage/bed/delete', method: 'delete', params: { id } }) }
export function updateBedStatus(id, status) { return request({ url: '/basic-manage/bed/status', method: 'put', params: { id, status } }) }

View File

@@ -1,42 +1,108 @@
<template>
<div class="app-container">
<el-card shadow="never">
<template #header><span class="card-title">床位管理</span></template>
<el-form :inline="true" :model="queryParams">
<el-form-item label="搜索">
<el-input v-model="queryParams.searchKey" placeholder="搜索" clearable @keyup.enter="handleQuery" />
<template #header>
<div class="card-header">
<span class="card-title">床位管理</span>
<el-button type="primary" icon="Plus" @click="handleAdd">新增床位</el-button>
</div>
</template>
<el-form :inline="true" :model="queryParams" label-width="80px">
<el-form-item label="床号">
<el-input v-model="queryParams.bedNo" placeholder="床号" clearable @keyup.enter="handleQuery" style="width: 140px" />
</el-form-item>
<el-form-item label="状态">
<el-select v-model="queryParams.status" placeholder="全部" clearable style="width: 120px">
<el-option label="空闲" :value="0" /><el-option label="占用" :value="1" />
<el-option label="清洁" :value="2" /><el-option label="维修" :value="3" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button type="success" icon="Plus" @click="handleAdd">新增</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<vxe-table :data="tableData" border height="calc(100vh - 280px)">
<vxe-table :data="tableData" border height="calc(100vh - 320px)" v-loading="loading">
<vxe-column type="seq" title="序号" width="60" />
<vxe-column field="name" title="名称" />
<vxe-column field="code" title="编码" />
<vxe-column field="status" title="状态">
<template #default="{ row }"><el-tag :type="row.status === '0' ? 'success' : 'info'">{{ row.status === '0' ? '正常' : '停用' }}</el-tag></template>
</vxe-column>
<vxe-column title="操作" width="150">
<vxe-column field="bedNo" title="床号" width="100" />
<vxe-column field="bedName" title="床位名" width="120" />
<vxe-column field="wardName" title="病区" width="120" />
<vxe-column field="deptName" title="科室" width="120" />
<vxe-column field="bedType" title="类型" width="80" align="center">
<template #default="{ row }">
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
<el-tag :type="row.bedType === 2 ? 'danger' : row.bedType === 3 ? 'warning' : 'info'" size="small">
{{ { 1: '普通', 2: 'ICU', 3: '急诊' }[row.bedType] || '普通' }}
</el-tag>
</template>
</vxe-column>
<vxe-column field="status" title="状态" width="80" align="center">
<template #default="{ row }">
<el-tag :type="statusType(row.status)" size="small">{{ statusText(row.status) }}</el-tag>
</template>
</vxe-column>
<vxe-column title="操作" width="220" fixed="right">
<template #default="{ row }">
<el-button type="primary" link icon="Edit" @click="handleEdit(row)">编辑</el-button>
<el-button v-if="row.status === 0" type="warning" link @click="changeStatus(row, 1)">占用</el-button>
<el-button v-if="row.status === 1" type="success" link @click="changeStatus(row, 2)">清洁</el-button>
<el-button v-if="row.status === 2" type="info" link @click="changeStatus(row, 0)">空闲</el-button>
<el-button v-if="row.status !== 3" type="danger" link @click="changeStatus(row, 3)">维修</el-button>
<el-button type="danger" link icon="Delete" @click="handleDelete(row)">删除</el-button>
</template>
</vxe-column>
</vxe-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
</el-card>
<el-dialog v-model="formVisible" :title="formTitle" width="550px" append-to-body>
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="床号" prop="bedNo"><el-input v-model="form.bedNo" placeholder="床号" /></el-form-item>
<el-form-item label="床位名"><el-input v-model="form.bedName" placeholder="床位名" /></el-form-item>
<el-form-item label="病区"><el-input v-model="form.wardName" placeholder="病区" /></el-form-item>
<el-form-item label="科室"><el-input v-model="form.deptName" placeholder="科室" /></el-form-item>
<el-form-item label="类型">
<el-select v-model="form.bedType" style="width:100%">
<el-option label="普通" :value="1" /><el-option label="ICU" :value="2" /><el-option label="急诊" :value="3" />
</el-select>
</el-form-item>
<el-form-item label="备注"><el-input v-model="form.remark" type="textarea" :rows="2" /></el-form-item>
</el-form>
<template #footer>
<el-button @click="formVisible = false">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
const queryParams = ref({ searchKey: '', pageNum: 1, pageSize: 20 })
const tableData = ref([])
const handleQuery = () => { tableData.value = [] }
const handleAdd = () => { ElMessage.info('新增功能开发中') }
const handleEdit = (row) => { ElMessage.info('编辑功能开发中') }
const handleDelete = (row) => { ElMessage.info('删除功能开发中') }
onMounted(() => handleQuery())
import { ElMessage, ElMessageBox } from 'element-plus'
import { getBedPage, addBed, updateBed, deleteBed, updateBedStatus } from './components/api'
const loading = ref(false); const tableData = ref([]); const total = ref(0)
const queryParams = ref({ bedNo: '', status: undefined, pageNo: 1, pageSize: 20 })
const formVisible = ref(false); const formTitle = ref('新增床位'); const isEdit = ref(false); const formRef = ref()
const form = ref({ id: null, bedNo: '', bedName: '', wardName: '', deptName: '', bedType: 1, remark: '' })
const rules = { bedNo: [{ required: true, message: '请输入床号', trigger: 'blur' }] }
const statusText = (s) => ({ 0: '空闲', 1: '占用', 2: '清洁', 3: '维修' }[s] || '未知')
const statusType = (s) => ({ 0: 'success', 1: 'danger', 2: 'info', 3: 'warning' }[s] || 'info')
function getList() {
loading.value = true
getBedPage(queryParams.value).then(res => { tableData.value = res.data?.records || []; total.value = res.data?.total || 0 }).finally(() => { loading.value = false })
}
function handleQuery() { queryParams.value.pageNo = 1; getList() }
function resetQuery() { queryParams.value = { bedNo: '', status: undefined, pageNo: 1, pageSize: 20 }; getList() }
function handleAdd() { isEdit.value = false; formTitle.value = '新增床位'; form.value = { id: null, bedNo: '', bedName: '', wardName: '', deptName: '', bedType: 1, remark: '' }; formVisible.value = true }
function handleEdit(row) { isEdit.value = true; formTitle.value = '编辑床位'; form.value = { ...row }; formVisible.value = true }
function submitForm() {
formRef.value.validate(valid => { if (!valid) return
const action = isEdit.value ? updateBed(form.value) : addBed(form.value)
action.then(res => { if (res.code === 200) { ElMessage.success(isEdit.value ? '修改成功' : '新增成功'); formVisible.value = false; getList() } else ElMessage.error(res.msg || '操作失败') })
})
}
function handleDelete(row) { ElMessageBox.confirm('确认删除该床位?', '提示', { type: 'warning' }).then(() => { deleteBed(row.id).then(res => { if (res.code === 200) { ElMessage.success('删除成功'); getList() } }) }).catch(() => {}) }
function changeStatus(row, status) { updateBedStatus(row.id, status).then(res => { if (res.code === 200) { ElMessage.success('状态已更新'); getList() } }) }
onMounted(() => getList())
</script>
<style scoped>.card-title { font-weight: bold; font-size: 16px; }</style>
<style lang="scss" scoped>
.card-header { display: flex; justify-content: space-between; align-items: center; }
.card-title { font-weight: bold; font-size: 16px; }
</style>