feat(blood-transfusion): 输血管理全流程

This commit is contained in:
2026-06-17 11:51:35 +08:00
parent d8e9da965b
commit d7455684db
14 changed files with 816 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
package com.healthlink.his.web.bloodtransfusion.appservice;
import com.healthlink.his.bloodtransfusion.domain.BloodTransfusionRecord;
import com.healthlink.his.bloodtransfusion.domain.BloodTransfusionObservation;
import com.baomidou.mybatisplus.core.metadata.IPage;
import java.util.Map;
public interface IBloodTransfusionAppService {
void apply(BloodTransfusionRecord record);
IPage<BloodTransfusionRecord> page(String patientName, String approvalStatus, String bloodComponent, Integer pageNum, Integer pageSize);
void approve(Long id, String approvalStatus, String approverName, String remark);
void observe(BloodTransfusionObservation observation);
Map<String, Object> getRecordDetail(Long id);
}

View File

@@ -0,0 +1,78 @@
package com.healthlink.his.web.bloodtransfusion.appservice.impl;
import com.healthlink.his.bloodtransfusion.domain.BloodTransfusionRecord;
import com.healthlink.his.bloodtransfusion.domain.BloodTransfusionObservation;
import com.healthlink.his.bloodtransfusion.service.IBloodTransfusionRecordService;
import com.healthlink.his.bloodtransfusion.service.IBloodTransfusionObservationService;
import com.healthlink.his.web.bloodtransfusion.appservice.IBloodTransfusionAppService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;
@Service
public class BloodTransfusionAppServiceImpl implements IBloodTransfusionAppService {
@Autowired
private IBloodTransfusionRecordService recordService;
@Autowired
private IBloodTransfusionObservationService observationService;
@Override
public void apply(BloodTransfusionRecord record) {
record.setStatus("SUBMITTED");
record.setApprovalStatus("PENDING");
record.setCreateTime(new Date());
recordService.save(record);
}
@Override
public IPage<BloodTransfusionRecord> page(String patientName, String approvalStatus, String bloodComponent, Integer pageNum, Integer pageSize) {
LambdaQueryWrapper<BloodTransfusionRecord> w = new LambdaQueryWrapper<>();
if (patientName != null && !patientName.isEmpty()) {
w.like(BloodTransfusionRecord::getDoctorName, patientName);
}
if (approvalStatus != null && !approvalStatus.isEmpty()) {
w.eq(BloodTransfusionRecord::getApprovalStatus, approvalStatus);
}
if (bloodComponent != null && !bloodComponent.isEmpty()) {
w.eq(BloodTransfusionRecord::getBloodComponent, bloodComponent);
}
w.orderByDesc(BloodTransfusionRecord::getCreateTime);
return recordService.page(new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(pageNum, pageSize), w);
}
@Override
public void approve(Long id, String approvalStatus, String approverName, String remark) {
BloodTransfusionRecord record = recordService.getById(id);
if (record == null) {
throw new RuntimeException("输血记录不存在");
}
record.setApprovalStatus(approvalStatus);
record.setApproverName(approverName);
record.setApproveTime(new Date());
if ("APPROVED".equals(approvalStatus)) {
record.setStatus("APPROVED");
} else if ("REJECTED".equals(approvalStatus)) {
record.setStatus("REJECTED");
}
recordService.updateById(record);
}
@Override
public void observe(BloodTransfusionObservation observation) {
observation.setCreateTime(new Date());
observationService.save(observation);
}
@Override
public Map<String, Object> getRecordDetail(Long id) {
Map<String, Object> result = new LinkedHashMap<>();
BloodTransfusionRecord record = recordService.getById(id);
if (record == null) {
result.put("error", "记录不存在");
return result;
}
result.put("record", record);
List<BloodTransfusionObservation> observations = observationService.list(
new LambdaQueryWrapper<BloodTransfusionObservation>()
.eq(BloodTransfusionObservation::getRecordId, id)
.orderByAsc(BloodTransfusionObservation::getObservationTime)
);
result.put("observations", observations);
return result;
}
}

View File

@@ -0,0 +1,55 @@
package com.healthlink.his.web.bloodtransfusion.controller;
import com.core.common.core.domain.AjaxResult;
import com.healthlink.his.bloodtransfusion.domain.BloodTransfusionRecord;
import com.healthlink.his.bloodtransfusion.domain.BloodTransfusionObservation;
import com.healthlink.his.web.bloodtransfusion.appservice.IBloodTransfusionAppService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@Tag(name = "输血管理") @RestController @RequestMapping("/api/v1/blood-transfusion")
public class BloodTransfusionController {
@Autowired
private IBloodTransfusionAppService appService;
@Operation(summary = "申请输血")
@PreAuthorize("@ss.hasPermi('inpatient:bloodtransfusion:edit')")
@PostMapping("/apply")
public AjaxResult apply(@RequestBody BloodTransfusionRecord record) {
appService.apply(record);
return AjaxResult.success();
}
@Operation(summary = "输血申请分页")
@PreAuthorize("@ss.hasPermi('inpatient:bloodtransfusion:list')")
@GetMapping("/page")
public AjaxResult page(@RequestParam(required = false) String patientName,
@RequestParam(required = false) String approvalStatus,
@RequestParam(required = false) String bloodComponent,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
return AjaxResult.success(appService.page(patientName, approvalStatus, bloodComponent, pageNum, pageSize));
}
@Operation(summary = "审批输血")
@PreAuthorize("@ss.hasPermi('inpatient:bloodtransfusion:edit')")
@PutMapping("/approve/{id}")
public AjaxResult approve(@PathVariable Long id,
@RequestParam String approvalStatus,
@RequestParam(required = false) String approverName,
@RequestParam(required = false) String remark) {
appService.approve(id, approvalStatus, approverName, remark);
return AjaxResult.success();
}
@Operation(summary = "输血观察记录")
@PreAuthorize("@ss.hasPermi('inpatient:bloodtransfusion:edit')")
@PostMapping("/observe")
public AjaxResult observe(@RequestBody BloodTransfusionObservation observation) {
appService.observe(observation);
return AjaxResult.success();
}
@Operation(summary = "输血记录详情")
@PreAuthorize("@ss.hasPermi('inpatient:bloodtransfusion:list')")
@GetMapping("/record/{id}")
public AjaxResult recordDetail(@PathVariable Long id) {
return AjaxResult.success(appService.getRecordDetail(id));
}
}

View File

@@ -0,0 +1,51 @@
CREATE TABLE blood_transfusion_record (
id BIGSERIAL PRIMARY KEY,
encounter_id BIGINT NOT NULL,
patient_id BIGINT NOT NULL,
advice_id BIGINT,
blood_type VARCHAR(10),
blood_component VARCHAR(50) NOT NULL,
blood_volume DECIMAL(10,2),
blood_unit VARCHAR(50),
cross_match_result VARCHAR(20),
indication TEXT,
doctor_id BIGINT NOT NULL,
doctor_name VARCHAR(50),
approval_status VARCHAR(20) DEFAULT 'PENDING',
approver_id BIGINT,
approver_name VARCHAR(50),
approve_time TIMESTAMP,
transfusion_start_time TIMESTAMP,
transfusion_end_time TIMESTAMP,
transfusion_nurse_id BIGINT,
transfusion_nurse_name VARCHAR(50),
pre_vital_signs TEXT,
during_vital_signs TEXT,
post_vital_signs TEXT,
adverse_reaction TEXT,
adverse_reaction_type VARCHAR(50),
status VARCHAR(20) DEFAULT 'DRAFT',
del_flag CHAR(1) DEFAULT '0',
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
create_by VARCHAR(64),
update_time TIMESTAMP,
update_by VARCHAR(64)
);
CREATE TABLE blood_transfusion_observation (
id BIGSERIAL PRIMARY KEY,
record_id BIGINT NOT NULL,
observation_time TIMESTAMP NOT NULL,
observation_phase VARCHAR(20),
temperature DECIMAL(4,1),
pulse INTEGER,
respiration INTEGER,
blood_pressure_high INTEGER,
blood_pressure_low INTEGER,
symptoms TEXT,
nurse_id BIGINT,
nurse_name VARCHAR(50),
del_flag CHAR(1) DEFAULT '0',
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
create_by VARCHAR(64)
);

View File

@@ -0,0 +1,30 @@
package com.healthlink.his.bloodtransfusion.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 tools.jackson.databind.annotation.JsonSerialize;
import tools.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.util.Date;
@Data
@TableName("blood_transfusion_observation")
@EqualsAndHashCode(callSuper = true)
public class BloodTransfusionObservation extends HisBaseEntity {
@TableId(type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
private Long recordId;
private Date observationTime;
private String observationPhase;
private BigDecimal temperature;
private Integer pulse;
private Integer respiration;
private Integer bloodPressureHigh;
private Integer bloodPressureLow;
private String symptoms;
private Long nurseId;
private String nurseName;
}

View File

@@ -0,0 +1,44 @@
package com.healthlink.his.bloodtransfusion.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 tools.jackson.databind.annotation.JsonSerialize;
import tools.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.util.Date;
@Data
@TableName("blood_transfusion_record")
@EqualsAndHashCode(callSuper = true)
public class BloodTransfusionRecord extends HisBaseEntity {
@TableId(type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
private Long encounterId;
private Long patientId;
private Long adviceId;
private String bloodType;
private String bloodComponent;
private BigDecimal bloodVolume;
private String bloodUnit;
private String crossMatchResult;
private String indication;
private Long doctorId;
private String doctorName;
private String approvalStatus;
private Long approverId;
private String approverName;
private Date approveTime;
private Date transfusionStartTime;
private Date transfusionEndTime;
private Long transfusionNurseId;
private String transfusionNurseName;
private String preVitalSigns;
private String duringVitalSigns;
private String postVitalSigns;
private String adverseReaction;
private String adverseReactionType;
private String status;
}

View File

@@ -0,0 +1,7 @@
package com.healthlink.his.bloodtransfusion.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.healthlink.his.bloodtransfusion.domain.BloodTransfusionObservation;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface BloodTransfusionObservationMapper extends BaseMapper<BloodTransfusionObservation> {
}

View File

@@ -0,0 +1,7 @@
package com.healthlink.his.bloodtransfusion.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.healthlink.his.bloodtransfusion.domain.BloodTransfusionRecord;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface BloodTransfusionRecordMapper extends BaseMapper<BloodTransfusionRecord> {
}

View File

@@ -0,0 +1,5 @@
package com.healthlink.his.bloodtransfusion.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.healthlink.his.bloodtransfusion.domain.BloodTransfusionObservation;
public interface IBloodTransfusionObservationService extends IService<BloodTransfusionObservation> {
}

View File

@@ -0,0 +1,5 @@
package com.healthlink.his.bloodtransfusion.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.healthlink.his.bloodtransfusion.domain.BloodTransfusionRecord;
public interface IBloodTransfusionRecordService extends IService<BloodTransfusionRecord> {
}

View File

@@ -0,0 +1,9 @@
package com.healthlink.his.bloodtransfusion.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.bloodtransfusion.domain.BloodTransfusionObservation;
import com.healthlink.his.bloodtransfusion.mapper.BloodTransfusionObservationMapper;
import com.healthlink.his.bloodtransfusion.service.IBloodTransfusionObservationService;
import org.springframework.stereotype.Service;
@Service
public class BloodTransfusionObservationServiceImpl extends ServiceImpl<BloodTransfusionObservationMapper, BloodTransfusionObservation> implements IBloodTransfusionObservationService {
}

View File

@@ -0,0 +1,9 @@
package com.healthlink.his.bloodtransfusion.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.bloodtransfusion.domain.BloodTransfusionRecord;
import com.healthlink.his.bloodtransfusion.mapper.BloodTransfusionRecordMapper;
import com.healthlink.his.bloodtransfusion.service.IBloodTransfusionRecordService;
import org.springframework.stereotype.Service;
@Service
public class BloodTransfusionRecordServiceImpl extends ServiceImpl<BloodTransfusionRecordMapper, BloodTransfusionRecord> implements IBloodTransfusionRecordService {
}

View File

@@ -0,0 +1,40 @@
import request from '@/utils/request'
export function applyBloodTransfusion(data) {
return request({
url: '/api/v1/blood-transfusion/apply',
method: 'post',
data: data
})
}
export function getBloodTransfusionPage(params) {
return request({
url: '/api/v1/blood-transfusion/page',
method: 'get',
params: params
})
}
export function approveBloodTransfusion(id, params) {
return request({
url: '/api/v1/blood-transfusion/approve/' + id,
method: 'put',
params: params
})
}
export function addObservation(data) {
return request({
url: '/api/v1/blood-transfusion/observe',
method: 'post',
data: data
})
}
export function getRecordDetail(id) {
return request({
url: '/api/v1/blood-transfusion/record/' + id,
method: 'get'
})
}

View File

@@ -0,0 +1,464 @@
<template>
<div class="blood-transfusion-container">
<el-card v-loading="loading">
<template #header>
<div class="card-header">
<span>输血管理</span>
<el-button type="primary" size="small" @click="handleApply">新建申请</el-button>
</div>
</template>
<el-form :inline="true" :model="queryParams" class="query-form">
<el-form-item label="血液成分">
<el-select v-model="queryParams.bloodComponent" clearable placeholder="请选择" style="width: 160px">
<el-option label="全血" value="全血" />
<el-option label="红细胞悬液" value="红细胞悬液" />
<el-option label="血浆" value="血浆" />
<el-option label="血小板" value="血小板" />
<el-option label="冷沉淀" value="冷沉淀" />
</el-select>
</el-form-item>
<el-form-item label="审批状态">
<el-select v-model="queryParams.approvalStatus" clearable placeholder="请选择" style="width: 140px">
<el-option label="待审批" value="PENDING" />
<el-option label="已通过" value="APPROVED" />
<el-option label="已驳回" value="REJECTED" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">查询</el-button>
<el-button @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-table :data="tableData" border style="width: 100%">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="bloodComponent" label="血液成分" width="140" />
<el-table-column prop="bloodVolume" label="血量" width="100" align="center" />
<el-table-column prop="bloodUnit" label="单位" width="80" align="center" />
<el-table-column prop="doctorName" label="申请医生" width="120" />
<el-table-column prop="indication" label="输血指征" min-width="160" show-overflow-tooltip />
<el-table-column prop="approvalStatus" label="审批状态" width="100" align="center">
<template #default="scope">
<el-tag v-if="scope.row.approvalStatus === 'PENDING'" type="warning">待审批</el-tag>
<el-tag v-else-if="scope.row.approvalStatus === 'APPROVED'" type="success">已通过</el-tag>
<el-tag v-else-if="scope.row.approvalStatus === 'REJECTED'" type="danger">已驳回</el-tag>
<el-tag v-else type="info">{{ scope.row.approvalStatus }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="100" align="center">
<template #default="scope">
<el-tag v-if="scope.row.status === 'SUBMITTED'" type="info">已提交</el-tag>
<el-tag v-else-if="scope.row.status === 'APPROVED'" type="success">已审批</el-tag>
<el-tag v-else-if="scope.row.status === 'REJECTED'" type="danger">已驳回</el-tag>
<el-tag v-else type="info">{{ scope.row.status }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="createTime" label="申请时间" width="170" />
<el-table-column label="操作" width="200" align="center" fixed="right">
<template #default="scope">
<el-button link type="primary" @click="handleDetail(scope.row)">详情</el-button>
<el-button v-if="scope.row.approvalStatus === 'PENDING'" link type="success" @click="handleApprove(scope.row)">审批</el-button>
<el-button v-if="scope.row.status === 'APPROVED'" link type="primary" @click="handleObserve(scope.row)">观察记录</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
v-model:current-page="queryParams.pageNum"
v-model:page-size="queryParams.pageSize"
:page-sizes="[10, 20, 50]"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
style="margin-top: 12px; justify-content: flex-end"
@size-change="handleQuery"
@current-change="handleQuery"
/>
</el-card>
<el-dialog v-model="applyDialogVisible" title="输血申请" width="700px" destroy-on-close :close-on-click-modal="false">
<el-form ref="applyFormRef" :model="applyForm" :rules="applyRules" label-width="110px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="血液成分" prop="bloodComponent">
<el-select v-model="applyForm.bloodComponent" placeholder="请选择" style="width: 100%">
<el-option label="全血" value="全血" />
<el-option label="红细胞悬液" value="红细胞悬液" />
<el-option label="血浆" value="血浆" />
<el-option label="血小板" value="血小板" />
<el-option label="冷沉淀" value="冷沉淀" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="血型" prop="bloodType">
<el-select v-model="applyForm.bloodType" placeholder="请选择" style="width: 100%">
<el-option label="A型" value="A" />
<el-option label="B型" value="B" />
<el-option label="AB型" value="AB" />
<el-option label="O型" value="O" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="血量" prop="bloodVolume">
<el-input-number v-model="applyForm.bloodVolume" :min="0" :precision="2" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="单位" prop="bloodUnit">
<el-select v-model="applyForm.bloodUnit" placeholder="请选择" style="width: 100%">
<el-option label="ml" value="ml" />
<el-option label="U" value="U" />
<el-option label="袋" value="袋" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="交叉配血" prop="crossMatchResult">
<el-select v-model="applyForm.crossMatchResult" placeholder="请选择" style="width: 100%">
<el-option label="待检测" value="PENDING" />
<el-option label="相合" value="COMPATIBLE" />
<el-option label="不合" value="INCOMPATIBLE" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="申请医生" prop="doctorName">
<el-input v-model="applyForm.doctorName" placeholder="请输入" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="输血指征" prop="indication">
<el-input v-model="applyForm.indication" type="textarea" :rows="3" placeholder="请输入输血指征" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<el-button @click="applyDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="submitLoading" @click="submitApply">提交申请</el-button>
</template>
</el-dialog>
<el-dialog v-model="approveDialogVisible" title="输血审批" width="500px" destroy-on-close :close-on-click-modal="false">
<el-form ref="approveFormRef" :model="approveForm" label-width="90px">
<el-form-item label="审批结果">
<el-radio-group v-model="approveForm.approvalStatus">
<el-radio value="APPROVED">通过</el-radio>
<el-radio value="REJECTED">驳回</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="审批人">
<el-input v-model="approveForm.approverName" placeholder="请输入审批人" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="approveDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="submitLoading" @click="submitApprove">确认</el-button>
</template>
</el-dialog>
<el-dialog v-model="observeDialogVisible" title="输血观察记录" width="700px" destroy-on-close :close-on-click-modal="false">
<el-form ref="observeFormRef" :model="observeForm" :rules="observeRules" label-width="100px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="观察时间" prop="observationTime">
<el-date-picker v-model="observeForm.observationTime" type="datetime" placeholder="选择时间" value-format="YYYY-MM-DD HH:mm:ss" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="观察阶段" prop="observationPhase">
<el-select v-model="observeForm.observationPhase" placeholder="请选择" style="width: 100%">
<el-option label="输血前" value="PRE" />
<el-option label="输血中" value="DURING" />
<el-option label="输血后" value="POST" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="体温(℃)">
<el-input-number v-model="observeForm.temperature" :min="35" :max="42" :step="0.1" :precision="1" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="脉搏(次/分)">
<el-input-number v-model="observeForm.pulse" :min="40" :max="200" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="呼吸(次/分)">
<el-input-number v-model="observeForm.respiration" :min="10" :max="50" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="收缩压(mmHg)">
<el-input-number v-model="observeForm.bloodPressureHigh" :min="60" :max="250" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="舒张压(mmHg)">
<el-input-number v-model="observeForm.bloodPressureLow" :min="30" :max="150" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="护士">
<el-input v-model="observeForm.nurseName" placeholder="请输入护士姓名" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="症状描述">
<el-input v-model="observeForm.symptoms" type="textarea" :rows="3" placeholder="请输入症状描述" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<el-button @click="observeDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="submitLoading" @click="submitObserve">保存</el-button>
</template>
</el-dialog>
<el-dialog v-model="detailDialogVisible" title="输血记录详情" width="800px" destroy-on-close top="5vh" :close-on-click-modal="false">
<div v-if="detailData.record" class="detail-content">
<el-descriptions title="申请信息" :column="2" border size="small">
<el-descriptions-item label="血液成分">{{ detailData.record.bloodComponent }}</el-descriptions-item>
<el-descriptions-item label="血型">{{ detailData.record.bloodType }}</el-descriptions-item>
<el-descriptions-item label="血量">{{ detailData.record.bloodVolume }} {{ detailData.record.bloodUnit }}</el-descriptions-item>
<el-descriptions-item label="交叉配血">{{ detailData.record.crossMatchResult }}</el-descriptions-item>
<el-descriptions-item label="申请医生">{{ detailData.record.doctorName }}</el-descriptions-item>
<el-descriptions-item label="审批状态">
<el-tag v-if="detailData.record.approvalStatus === 'PENDING'" type="warning">待审批</el-tag>
<el-tag v-else-if="detailData.record.approvalStatus === 'APPROVED'" type="success">已通过</el-tag>
<el-tag v-else-if="detailData.record.approvalStatus === 'REJECTED'" type="danger">已驳回</el-tag>
</el-descriptions-item>
<el-descriptions-item label="审批人">{{ detailData.record.approverName || '-' }}</el-descriptions-item>
<el-descriptions-item label="审批时间">{{ detailData.record.approveTime || '-' }}</el-descriptions-item>
<el-descriptions-item label="输血指征" :span="2">{{ detailData.record.indication }}</el-descriptions-item>
</el-descriptions>
<el-descriptions v-if="detailData.record.transfusionStartTime" title="执行信息" :column="2" border size="small" style="margin-top: 16px">
<el-descriptions-item label="开始时间">{{ detailData.record.transfusionStartTime }}</el-descriptions-item>
<el-descriptions-item label="结束时间">{{ detailData.record.transfusionEndTime || '-' }}</el-descriptions-item>
<el-descriptions-item label="输血护士">{{ detailData.record.transfusionNurseName || '-' }}</el-descriptions-item>
<el-descriptions-item label="不良反应">{{ detailData.record.adverseReaction || '无' }}</el-descriptions-item>
</el-descriptions>
<div v-if="detailData.observations && detailData.observations.length > 0" style="margin-top: 16px">
<el-table :data="detailData.observations" border size="small">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="observationTime" label="观察时间" width="170" />
<el-table-column prop="observationPhase" label="阶段" width="80" align="center">
<template #default="scope">
<el-tag v-if="scope.row.observationPhase === 'PRE'" type="info"></el-tag>
<el-tag v-else-if="scope.row.observationPhase === 'DURING'" type="warning"></el-tag>
<el-tag v-else-if="scope.row.observationPhase === 'POST'" type="success"></el-tag>
<el-tag v-else>{{ scope.row.observationPhase }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="temperature" label="体温" width="80" align="center" />
<el-table-column prop="pulse" label="脉搏" width="80" align="center" />
<el-table-column prop="respiration" label="呼吸" width="80" align="center" />
<el-table-column label="血压" width="110" align="center">
<template #default="scope">{{ scope.row.bloodPressureHigh }}/{{ scope.row.bloodPressureLow }}</template>
</el-table-column>
<el-table-column prop="nurseName" label="护士" width="100" />
<el-table-column prop="symptoms" label="症状" min-width="150" show-overflow-tooltip />
</el-table>
</div>
</div>
<template #footer>
<el-button @click="detailDialogVisible = false">关闭</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { applyBloodTransfusion, getBloodTransfusionPage, approveBloodTransfusion, addObservation, getRecordDetail } from '@/api/bloodtransfusion'
const loading = ref(false)
const submitLoading = ref(false)
const tableData = ref([])
const total = ref(0)
const queryParams = reactive({ pageNum: 1, pageSize: 10, bloodComponent: '', approvalStatus: '' })
const applyDialogVisible = ref(false)
const approveDialogVisible = ref(false)
const observeDialogVisible = ref(false)
const detailDialogVisible = ref(false)
const applyFormRef = ref(null)
const approveFormRef = ref(null)
const observeFormRef = ref(null)
const applyForm = reactive({
bloodComponent: '', bloodType: '', bloodVolume: null, bloodUnit: 'ml',
crossMatchResult: 'PENDING', doctorName: '', indication: '',
encounterId: null, patientId: null
})
const applyRules = {
bloodComponent: [{ required: true, message: '请选择血液成分', trigger: 'change' }],
bloodType: [{ required: true, message: '请选择血型', trigger: 'change' }],
bloodVolume: [{ required: true, message: '请输入血量', trigger: 'blur' }],
bloodUnit: [{ required: true, message: '请选择单位', trigger: 'change' }],
doctorName: [{ required: true, message: '请输入申请医生', trigger: 'blur' }],
indication: [{ required: true, message: '请输入输血指征', trigger: 'blur' }]
}
const approveForm = reactive({ approvalStatus: 'APPROVED', approverName: '' })
const observeForm = reactive({
recordId: null, observationTime: '', observationPhase: '',
temperature: null, pulse: null, respiration: null,
bloodPressureHigh: null, bloodPressureLow: null, symptoms: '', nurseName: ''
})
const observeRules = {
observationTime: [{ required: true, message: '请选择观察时间', trigger: 'change' }],
observationPhase: [{ required: true, message: '请选择观察阶段', trigger: 'change' }]
}
const detailData = ref({ record: null, observations: [] })
let currentRecordId = null
function handleQuery() {
loading.value = true
getBloodTransfusionPage(queryParams).then(res => {
tableData.value = res.data?.records || []
total.value = res.data?.total || 0
}).catch(() => {
ElMessage.error('查询失败')
}).finally(() => {
loading.value = false
})
}
function resetQuery() {
queryParams.bloodComponent = ''
queryParams.approvalStatus = ''
queryParams.pageNum = 1
handleQuery()
}
function handleApply() {
applyForm.bloodComponent = ''
applyForm.bloodType = ''
applyForm.bloodVolume = null
applyForm.bloodUnit = 'ml'
applyForm.crossMatchResult = 'PENDING'
applyForm.doctorName = ''
applyForm.indication = ''
applyDialogVisible.value = true
}
function submitApply() {
applyFormRef.value?.validate(valid => {
if (!valid) return
submitLoading.value = true
applyBloodTransfusion({ ...applyForm }).then(res => {
if (res.code === 200) {
ElMessage.success('申请提交成功')
applyDialogVisible.value = false
handleQuery()
} else {
ElMessage.error(res.msg || '提交失败')
}
}).catch(() => {
ElMessage.error('提交失败')
}).finally(() => {
submitLoading.value = false
})
})
}
function handleApprove(row) {
currentRecordId = row.id
approveForm.approvalStatus = 'APPROVED'
approveForm.approverName = ''
approveDialogVisible.value = true
}
function submitApprove() {
submitLoading.value = true
approveBloodTransfusion(currentRecordId, {
approvalStatus: approveForm.approvalStatus,
approverName: approveForm.approverName
}).then(res => {
if (res.code === 200) {
ElMessage.success('审批完成')
approveDialogVisible.value = false
handleQuery()
} else {
ElMessage.error(res.msg || '审批失败')
}
}).catch(() => {
ElMessage.error('审批失败')
}).finally(() => {
submitLoading.value = false
})
}
function handleObserve(row) {
currentRecordId = row.id
observeForm.recordId = row.id
observeForm.observationTime = ''
observeForm.observationPhase = ''
observeForm.temperature = null
observeForm.pulse = null
observeForm.respiration = null
observeForm.bloodPressureHigh = null
observeForm.bloodPressureLow = null
observeForm.symptoms = ''
observeForm.nurseName = ''
observeDialogVisible.value = true
}
function submitObserve() {
observeFormRef.value?.validate(valid => {
if (!valid) return
submitLoading.value = true
addObservation({ ...observeForm }).then(res => {
if (res.code === 200) {
ElMessage.success('观察记录已保存')
observeDialogVisible.value = false
} else {
ElMessage.error(res.msg || '保存失败')
}
}).catch(() => {
ElMessage.error('保存失败')
}).finally(() => {
submitLoading.value = false
})
})
}
function handleDetail(row) {
loading.value = true
getRecordDetail(row.id).then(res => {
detailData.value = res.data || { record: null, observations: [] }
detailDialogVisible.value = true
}).catch(() => {
ElMessage.error('查询详情失败')
}).finally(() => {
loading.value = false
})
}
onMounted(() => {
handleQuery()
})
</script>
<style scoped>
.blood-transfusion-container {
padding: 12px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.query-form {
margin-bottom: 0;
}
.detail-content {
max-height: 70vh;
overflow-y: auto;
}
</style>