feat(critical-value): 危急值处理记录闭环

This commit is contained in:
2026-06-17 12:25:27 +08:00
parent f79c5a2c26
commit fc892e96dc
10 changed files with 341 additions and 0 deletions

View File

@@ -1,5 +1,6 @@
package com.healthlink.his.web.criticalvalue.appservice;
import com.healthlink.his.criticalvalue.domain.CriticalValue;
import com.healthlink.his.criticalvalue.domain.CriticalValueHandleRecord;
import java.util.List;
import java.util.Map;
public interface ICriticalValueAppService {
@@ -10,4 +11,7 @@ public interface ICriticalValueAppService {
List<CriticalValue> getPendingList();
List<CriticalValue> getOverdueList();
Map<String, Object> getStatistics(String startDate, String endDate);
List<CriticalValue> getPendingHandleList();
void handleCriticalValue(CriticalValueHandleRecord record);
List<CriticalValueHandleRecord> getHandleHistory(Long criticalValueId);
}

View File

@@ -1,14 +1,18 @@
package com.healthlink.his.web.criticalvalue.appservice.impl;
import com.healthlink.his.criticalvalue.domain.CriticalValue;
import com.healthlink.his.criticalvalue.domain.CriticalValueHandleRecord;
import com.healthlink.his.criticalvalue.service.ICriticalValueHandleRecordService;
import com.healthlink.his.criticalvalue.service.ICriticalValueService;
import com.healthlink.his.web.criticalvalue.appservice.ICriticalValueAppService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
@Service
public class CriticalValueAppServiceImpl implements ICriticalValueAppService {
@Autowired private ICriticalValueService criticalValueService;
@Autowired private ICriticalValueHandleRecordService handleRecordService;
@Override
public CriticalValue reportCriticalValue(CriticalValue cv) {
@@ -72,4 +76,38 @@ public class CriticalValueAppServiceImpl implements ICriticalValueAppService {
result.put("confirmRate", total > 0 ? Math.round(closed * 100.0 / total) : 0);
return result;
}
@Override
public List<CriticalValue> getPendingHandleList() {
return criticalValueService.list(new LambdaQueryWrapper<CriticalValue>()
.in(CriticalValue::getStatus, "PENDING", "RECEIVED")
.eq(CriticalValue::getDeleteFlag, "0")
.orderByDesc(CriticalValue::getReportTime));
}
@Override
@Transactional(rollbackFor = Exception.class)
public void handleCriticalValue(CriticalValueHandleRecord record) {
record.setHandleTime(new Date());
handleRecordService.save(record);
if ("HANDLE".equals(record.getHandleType())) {
CriticalValue cv = criticalValueService.getById(record.getCriticalValueId());
cv.setHandlerId(record.getHandlerId());
cv.setHandlerName(record.getHandlerName());
cv.setHandleTime(new Date());
cv.setHandleResult(record.getHandleResult());
cv.setStatus("PROCESSING");
criticalValueService.updateById(cv);
} else if ("CONFIRM".equals(record.getHandleType())) {
CriticalValue cv = criticalValueService.getById(record.getCriticalValueId());
cv.setCloseTime(new Date());
cv.setStatus("CLOSED");
criticalValueService.updateById(cv);
}
}
@Override
public List<CriticalValueHandleRecord> getHandleHistory(Long criticalValueId) {
return handleRecordService.list(new LambdaQueryWrapper<CriticalValueHandleRecord>()
.eq(CriticalValueHandleRecord::getCriticalValueId, criticalValueId)
.eq(CriticalValueHandleRecord::getDeleteFlag, "0")
.orderByDesc(CriticalValueHandleRecord::getHandleTime));
}
}

View File

@@ -1,10 +1,12 @@
package com.healthlink.his.web.criticalvalue.controller;
import com.core.common.core.domain.AjaxResult;
import com.healthlink.his.criticalvalue.domain.CriticalValue;
import com.healthlink.his.criticalvalue.domain.CriticalValueHandleRecord;
import com.healthlink.his.web.criticalvalue.appservice.ICriticalValueAppService;
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
@@ -42,4 +44,21 @@ public class CriticalValueController {
@GetMapping("/statistics")
public AjaxResult statistics(@RequestParam(required = false) String startDate, @RequestParam(required = false) String endDate) {
return AjaxResult.success(criticalValueAppService.getStatistics(startDate, endDate)); }
@Operation(summary = "待处理危急值列表")
@PreAuthorize("@ss.hasPermi('inpatient:criticalvalue:list')")
@GetMapping("/pending-handle")
public AjaxResult pendingHandle() { return AjaxResult.success(criticalValueAppService.getPendingHandleList()); }
@Operation(summary = "处理危急值")
@PreAuthorize("@ss.hasPermi('inpatient:criticalvalue:edit')")
@PostMapping("/handle")
public AjaxResult handle(@RequestBody CriticalValueHandleRecord record) {
criticalValueAppService.handleCriticalValue(record); return AjaxResult.success(); }
@Operation(summary = "处理历史")
@PreAuthorize("@ss.hasPermi('inpatient:criticalvalue:list')")
@GetMapping("/history")
public AjaxResult history(@RequestParam Long criticalValueId) {
return AjaxResult.success(criticalValueAppService.getHandleHistory(criticalValueId)); }
}

View File

@@ -0,0 +1,23 @@
CREATE TABLE critical_value_handle_record (
id BIGSERIAL PRIMARY KEY,
critical_value_id BIGINT NOT NULL,
encounter_id BIGINT NOT NULL,
patient_id BIGINT NOT NULL,
handle_type VARCHAR(20) NOT NULL,
handler_id BIGINT NOT NULL,
handler_name VARCHAR(50),
handle_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
handle_result TEXT,
review_result TEXT,
doctor_id BIGINT,
doctor_name VARCHAR(50),
confirm_time TIMESTAMP,
tenant_id BIGINT DEFAULT 0,
delete_flag CHAR(1) DEFAULT '0',
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
create_by VARCHAR(64)
);
COMMENT ON TABLE critical_value_handle_record IS '危急值处理记录';
COMMENT ON COLUMN critical_value_handle_record.handle_type IS '处理类型: HANDLE-处理 REVIEW-复查 CONFIRM-确认';
CREATE INDEX idx_cv_handle_cv_id ON critical_value_handle_record(critical_value_id);
CREATE INDEX idx_cv_handle_patient ON critical_value_handle_record(patient_id);

View File

@@ -0,0 +1,29 @@
package com.healthlink.his.criticalvalue.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;
import lombok.experimental.Accessors;
import java.util.Date;
@Data
@TableName("critical_value_handle_record")
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
public class CriticalValueHandleRecord extends HisBaseEntity {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
private Long criticalValueId;
private Long encounterId;
private Long patientId;
private String handleType;
private Long handlerId;
private String handlerName;
private Date handleTime;
private String handleResult;
private String reviewResult;
private Long doctorId;
private String doctorName;
private Date confirmTime;
}

View File

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

View File

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

View File

@@ -0,0 +1,8 @@
package com.healthlink.his.criticalvalue.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.criticalvalue.domain.CriticalValueHandleRecord;
import com.healthlink.his.criticalvalue.mapper.CriticalValueHandleRecordMapper;
import com.healthlink.his.criticalvalue.service.ICriticalValueHandleRecordService;
import org.springframework.stereotype.Service;
@Service
public class CriticalValueHandleRecordServiceImpl extends ServiceImpl<CriticalValueHandleRecordMapper, CriticalValueHandleRecord> implements ICriticalValueHandleRecordService {}

View File

@@ -4,3 +4,6 @@ export function confirmValue(id, params) { return request({ url: '/api/v1/critic
export function closeValue(id) { return request({ url: '/api/v1/critical-value/close/' + id, method: 'put' }) }
export function getStatistics() { return request({ url: '/api/v1/critical-value/statistics', method: 'get' }) }
export function getOverdueList() { return request({ url: '/api/v1/critical-value/overdue', method: 'get' }) }
export function getPendingHandleList() { return request({ url: '/api/v1/critical-value/pending-handle', method: 'get' }) }
export function handleCriticalValue(data) { return request({ url: '/api/v1/critical-value/handle', method: 'post', data: data }) }
export function getHandleHistory(criticalValueId) { return request({ url: '/api/v1/critical-value/history', method: 'get', params: { criticalValueId } }) }

View File

@@ -0,0 +1,207 @@
<template>
<div class="critical-value-handle-container">
<el-card v-loading="loading">
<template #header>
<div class="card-header">
<span>危急值处理</span>
<el-button type="primary" size="small" @click="handleQuery">刷新</el-button>
</div>
</template>
<el-table :data="tableData" border style="width: 100%">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="patientName" label="患者姓名" width="120" />
<el-table-column prop="itemName" label="危急项目" min-width="160" show-overflow-tooltip />
<el-table-column prop="resultValue" label="结果" width="100" align="center" />
<el-table-column prop="referenceRange" label="参考范围" width="120" />
<el-table-column prop="labDepartment" label="检验科室" width="120" />
<el-table-column prop="reportTime" label="报告时间" width="170" />
<el-table-column prop="status" label="状态" width="100" align="center">
<template #default="scope">
<el-tag v-if="scope.row.status === 'PENDING'" type="danger">待处理</el-tag>
<el-tag v-else-if="scope.row.status === 'RECEIVED'" type="warning">已接收</el-tag>
<el-tag v-else-if="scope.row.status === 'PROCESSING'" type="primary">处理中</el-tag>
<el-tag v-else-if="scope.row.status === 'CLOSED'" type="success">已关闭</el-tag>
<el-tag v-else type="info">{{ scope.row.status }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="200" align="center" fixed="right">
<template #default="scope">
<el-button v-if="scope.row.status !== 'CLOSED'" link type="primary" @click="handleProcess(scope.row)">处理</el-button>
<el-button link type="info" @click="handleHistory(scope.row)">历史</el-button>
</template>
</el-table-column>
</el-table>
<el-empty v-if="!loading && tableData.length === 0" description="暂无待处理危急值" />
</el-card>
<el-dialog v-model="processDialogVisible" title="危急值处理" width="600px" destroy-on-close :close-on-click-modal="false">
<el-form ref="processFormRef" :model="processForm" :rules="processRules" label-width="100px">
<el-form-item label="患者">
<el-input :model-value="currentRow?.patientName" disabled />
</el-form-item>
<el-form-item label="危急项目">
<el-input :model-value="currentRow?.itemName" disabled />
</el-form-item>
<el-form-item label="结果">
<el-input :model-value="currentRow?.resultValue" disabled />
</el-form-item>
<el-form-item label="处理类型" prop="handleType">
<el-select v-model="processForm.handleType" placeholder="请选择" style="width: 100%">
<el-option label="处理" value="HANDLE" />
<el-option label="复查" value="REVIEW" />
<el-option label="确认关闭" value="CONFIRM" />
</el-select>
</el-form-item>
<el-form-item label="处理人" prop="handlerName">
<el-input v-model="processForm.handlerName" placeholder="请输入处理人姓名" />
</el-form-item>
<el-form-item v-if="processForm.handleType === 'HANDLE'" label="处理结果" prop="handleResult">
<el-input v-model="processForm.handleResult" type="textarea" :rows="3" placeholder="请输入处理结果" />
</el-form-item>
<el-form-item v-if="processForm.handleType === 'REVIEW'" label="复查结果" prop="reviewResult">
<el-input v-model="processForm.reviewResult" type="textarea" :rows="3" placeholder="请输入复查结果" />
</el-form-item>
<el-form-item v-if="processForm.handleType === 'CONFIRM'" label="审核医生" prop="doctorName">
<el-input v-model="processForm.doctorName" placeholder="请输入审核医生姓名" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="processDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="submitLoading" @click="submitProcess">确认</el-button>
</template>
</el-dialog>
<el-dialog v-model="historyDialogVisible" title="处理历史" width="700px" destroy-on-close>
<el-table :data="historyData" border style="width: 100%" size="small">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="handleType" label="类型" width="100" align="center">
<template #default="scope">
<el-tag v-if="scope.row.handleType === 'HANDLE'" type="primary">处理</el-tag>
<el-tag v-else-if="scope.row.handleType === 'REVIEW'" type="warning">复查</el-tag>
<el-tag v-else-if="scope.row.handleType === 'CONFIRM'" type="success">确认</el-tag>
<el-tag v-else type="info">{{ scope.row.handleType }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="handlerName" label="处理人" width="100" />
<el-table-column prop="handleResult" label="处理结果" min-width="150" show-overflow-tooltip />
<el-table-column prop="reviewResult" label="复查结果" min-width="150" show-overflow-tooltip />
<el-table-column prop="doctorName" label="审核医生" width="100" />
<el-table-column prop="handleTime" label="处理时间" width="170" />
</el-table>
<el-empty v-if="historyData.length === 0" description="暂无处理记录" />
<template #footer>
<el-button @click="historyDialogVisible = false">关闭</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { getPendingHandleList, handleCriticalValue, getHandleHistory } from '@/api/criticalvalue'
const loading = ref(false)
const submitLoading = ref(false)
const tableData = ref([])
const processDialogVisible = ref(false)
const historyDialogVisible = ref(false)
const processFormRef = ref(null)
const currentRow = ref(null)
const processForm = reactive({
handleType: 'HANDLE',
handlerName: '',
handleResult: '',
reviewResult: '',
doctorName: ''
})
const processRules = {
handleType: [{ required: true, message: '请选择处理类型', trigger: 'change' }],
handlerName: [{ required: true, message: '请输入处理人', trigger: 'blur' }],
handleResult: [{ required: true, message: '请输入处理结果', trigger: 'blur' }],
reviewResult: [{ required: true, message: '请输入复查结果', trigger: 'blur' }],
doctorName: [{ required: true, message: '请输入审核医生', trigger: 'blur' }]
}
const historyData = ref([])
function handleQuery() {
loading.value = true
getPendingHandleList().then(res => {
tableData.value = res.data || []
}).catch(() => {
ElMessage.error('查询失败')
}).finally(() => {
loading.value = false
})
}
function handleProcess(row) {
currentRow.value = row
processForm.handleType = 'HANDLE'
processForm.handlerName = ''
processForm.handleResult = ''
processForm.reviewResult = ''
processForm.doctorName = ''
processDialogVisible.value = true
}
function submitProcess() {
processFormRef.value?.validate(valid => {
if (!valid) return
submitLoading.value = true
const payload = {
criticalValueId: currentRow.value.id,
encounterId: currentRow.value.encounterId,
patientId: currentRow.value.patientId,
handleType: processForm.handleType,
handlerId: null,
handlerName: processForm.handlerName,
handleResult: processForm.handleResult,
reviewResult: processForm.reviewResult,
doctorId: null,
doctorName: processForm.doctorName
}
handleCriticalValue(payload).then(res => {
if (res.code === 200) {
ElMessage.success('处理成功')
processDialogVisible.value = false
handleQuery()
} else {
ElMessage.error(res.msg || '处理失败')
}
}).catch(() => {
ElMessage.error('处理失败')
}).finally(() => {
submitLoading.value = false
})
})
}
function handleHistory(row) {
getHandleHistory(row.id).then(res => {
historyData.value = res.data || []
historyDialogVisible.value = true
}).catch(() => {
ElMessage.error('查询历史失败')
})
}
onMounted(() => {
handleQuery()
})
</script>
<style scoped>
.critical-value-handle-container {
padding: 12px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>