feat(nursing): 护理文书+质量指标+交接班增强

- 护理文书: 已有完整实现(NursingRecordController+前端),无需新增
- 护理质量指标: 新增 /nursing-quality/collect 采集指标, /nursing-quality/indicators 查询指标
- 交接班: 新增 /nursing-execution/handoff/key-patients 重点患者列表
- 前端: nursingquality 新增采集按钮, nursingexecution 交接班tab增加重点患者提示
This commit is contained in:
2026-06-18 12:26:08 +08:00
parent 0a865dd0d5
commit f990726def
6 changed files with 130 additions and 4 deletions

View File

@@ -78,6 +78,31 @@ public class NursingExecutionController {
return R.ok();
}
@GetMapping("/handoff/key-patients")
public R<?> getKeyPatients(
@RequestParam(value = "ward", required = false) String ward) {
LambdaQueryWrapper<NursingHandoffRecord> w = new LambdaQueryWrapper<>();
w.eq(StringUtils.hasText(ward), NursingHandoffRecord::getWard, ward)
.isNotNull(NursingHandoffRecord::getKeyPatients)
.ne(NursingHandoffRecord::getKeyPatients, "")
.orderByDesc(NursingHandoffRecord::getHandoffDate)
.last("LIMIT 20");
List<NursingHandoffRecord> records = handoffService.list(w);
List<Map<String, Object>> result = new ArrayList<>();
for (NursingHandoffRecord r : records) {
Map<String, Object> item = new HashMap<>();
item.put("ward", r.getWard());
item.put("shift", r.getShift());
item.put("handoffDate", r.getHandoffDate());
item.put("handoffNurseName", r.getHandoffNurseName());
item.put("keyPatients", r.getKeyPatients());
item.put("pendingMatters", r.getPendingMatters());
item.put("specialNotes", r.getSpecialNotes());
result.add(item);
}
return R.ok(result);
}
// ==================== 输液巡视 ====================
@GetMapping("/infusion/page")
public R<?> getInfusionPage(

View File

@@ -41,6 +41,61 @@ public class NursingQualityController {
return R.ok(indicator);
}
@GetMapping("/indicators")
public R<?> getIndicators(
@RequestParam(value = "indicatorCategory", required = false) String category,
@RequestParam(value = "departmentName", required = false) String departmentName,
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(value = "pageSize", defaultValue = "20") Integer pageSize) {
LambdaQueryWrapper<NursingQualityIndicator> w = new LambdaQueryWrapper<>();
w.eq(StringUtils.hasText(category), NursingQualityIndicator::getIndicatorCategory, category)
.eq(StringUtils.hasText(departmentName), NursingQualityIndicator::getDepartmentName, departmentName)
.orderByDesc(NursingQualityIndicator::getStatDate);
return R.ok(indicatorService.page(new Page<>(pageNo, pageSize), w));
}
@PostMapping("/collect")
@Transactional(rollbackFor = Exception.class)
public R<?> collectIndicators(@RequestBody Map<String, Object> params) {
String departmentName = (String) params.getOrDefault("departmentName", "");
String statPeriod = (String) params.getOrDefault("statPeriod", "MONTHLY");
String statDate = (String) params.getOrDefault("statDate", new java.text.SimpleDateFormat("yyyy-MM-dd").format(new Date()));
List<Map<String, Object>> rules = List.of(
Map.of("code", "NQ001", "name", "基础护理合格率", "category", "BASIC", "target", new java.math.BigDecimal("95"), "unit", "%"),
Map.of("code", "NQ002", "name", "护理文书书写合格率", "category", "DOCUMENTATION", "target", new java.math.BigDecimal("98"), "unit", "%"),
Map.of("code", "NQ003", "name", "急救物品完好率", "category", "SAFETY", "target", new java.math.BigDecimal("100"), "unit", "%"),
Map.of("code", "NQ004", "name", "消毒隔离合格率", "category", "STERILIZATION", "target", new java.math.BigDecimal("100"), "unit", "%"),
Map.of("code", "NQ005", "name", "压疮发生率", "category", "BASIC", "target", new java.math.BigDecimal("0"), "unit", "%"),
Map.of("code", "NQ006", "name", "跌倒发生率", "category", "SAFETY", "target", new java.math.BigDecimal("0"), "unit", "%"),
Map.of("code", "NQ007", "name", "患者满意度", "category", "BASIC", "target", new java.math.BigDecimal("90"), "unit", "%"),
Map.of("code", "NQ008", "name", "护理操作并发症发生率", "category", "SAFETY", "target", new java.math.BigDecimal("1"), "unit", "%")
);
int created = 0;
for (Map<String, Object> rule : rules) {
LambdaQueryWrapper<NursingQualityIndicator> exist = new LambdaQueryWrapper<>();
exist.eq(NursingQualityIndicator::getIndicatorCode, rule.get("code"))
.eq(NursingQualityIndicator::getStatDate, statDate);
if (indicatorService.count(exist) > 0) continue;
NursingQualityIndicator indicator = new NursingQualityIndicator();
indicator.setIndicatorCode((String) rule.get("code"));
indicator.setIndicatorName((String) rule.get("name"));
indicator.setIndicatorCategory((String) rule.get("category"));
indicator.setTargetValue((java.math.BigDecimal) rule.get("target"));
indicator.setUnit((String) rule.get("unit"));
indicator.setStatPeriod(statPeriod);
indicator.setStatDate(statDate);
indicator.setDepartmentName(departmentName);
indicator.setStatus("ACTIVE");
indicator.setCreateTime(new Date());
indicatorService.save(indicator);
created++;
}
return R.ok(Map.of("created", created, "total", rules.size()));
}
@GetMapping("/summary")
public R<?> getSummary() {
Map<String, Object> summary = new HashMap<>();

View File

@@ -4,5 +4,6 @@ export function addScan(d){return request({url:'/nursing-execution/scan/add',met
export function getHandoffPage(p){return request({url:'/nursing-execution/handoff/page',method:'get',params:p})}
export function addHandoff(d){return request({url:'/nursing-execution/handoff/add',method:'post',data:d})}
export function confirmHandoff(id){return request({url:'/nursing-execution/handoff/confirm',method:'post',params:{id}})}
export function getKeyPatients(p){return request({url:'/nursing-execution/handoff/key-patients',method:'get',params:p})}
export function getInfusionPage(p){return request({url:'/nursing-execution/infusion/page',method:'get',params:p})}
export function addInfusion(d){return request({url:'/nursing-execution/infusion/add',method:'post',data:d})}

View File

@@ -83,6 +83,33 @@
新增交接
</el-button>
</div>
<!-- 重点患者区域 -->
<div
v-if="keyPatients.length > 0"
style="margin-bottom:16px;background:#fdf6ec;border:1px solid #e6a23c;border-radius:6px;padding:12px"
>
<div style="font-weight:bold;color:#e6a23c;margin-bottom:8px">
重点患者提示
</div>
<div
v-for="(kp, idx) in keyPatients"
:key="idx"
style="margin-bottom:6px;font-size:13px"
>
<el-tag
type="warning"
size="small"
style="margin-right:6px"
>
{{ kp.ward }} {{ kp.shift }}
</el-tag>
<span style="font-weight:500">{{ kp.keyPatients }}</span>
<span
v-if="kp.pendingMatters"
style="color:#999;margin-left:8px"
>待办: {{ kp.pendingMatters }}</span>
</div>
</div>
<el-table
:data="handoffData"
border
@@ -224,14 +251,14 @@
<script setup>
import {ref,reactive,onMounted} from 'vue'
import {ElMessage} from 'element-plus'
import {getScanPage,addScan,getHandoffPage,addHandoff,confirmHandoff,getInfusionPage,addInfusion} from './api'
import {getScanPage,addScan,getHandoffPage,addHandoff,confirmHandoff,getKeyPatients,getInfusionPage,addInfusion} from './api'
const tab=ref('scan')
const scanData=ref([]),handoffData=ref([]),infusionData=ref([])
const scanData=ref([]),handoffData=ref([]),infusionData=ref([]),keyPatients=ref([])
const showScan=ref(false),showHandoff=ref(false),showInfusion=ref(false)
const scanForm=reactive({patientName:'',scanType:'WRISTBAND',barcode:'',nurseName:''})
const handoffForm=reactive({ward:'',shift:'MORNING',handoffNurseName:'',receiveNurseName:'',patientCount:0,criticalCount:0,keyPatients:''})
const infusionForm=reactive({patientName:'',drugName:'',dripRate:0,patencyStatus:'NORMAL',patrolNurseName:''})
const loadData=async()=>{const [s,h,i]=await Promise.all([getScanPage({pageNo:1,pageSize:50}),getHandoffPage({pageNo:1,pageSize:50}),getInfusionPage({pageNo:1,pageSize:50})]);scanData.value=s.data?.records||[];handoffData.value=h.data?.records||[];infusionData.value=i.data?.records||[]}
const loadData=async()=>{const [s,h,kp,i]=await Promise.all([getScanPage({pageNo:1,pageSize:50}),getHandoffPage({pageNo:1,pageSize:50}),getKeyPatients(),getInfusionPage({pageNo:1,pageSize:50})]);scanData.value=s.data?.records||[];handoffData.value=h.data?.records||[];keyPatients.value=kp.data||[];infusionData.value=i.data?.records||[]}
const confirmAction=async(row)=>{await confirmHandoff(row.id);ElMessage.success('已确认');loadData()}
const submitScan=async()=>{await addScan(scanForm);ElMessage.success('成功');showScan.value=false;loadData()}
const submitHandoff=async()=>{await addHandoff(handoffForm);ElMessage.success('成功');showHandoff.value=false;loadData()}

View File

@@ -2,3 +2,5 @@ import request from '@/utils/request'
export function getQualityPage(p){return request({url:'/nursing-quality/page',method:'get',params:p})}
export function addIndicator(d){return request({url:'/nursing-quality/add',method:'post',data:d})}
export function getQualitySummary(){return request({url:'/nursing-quality/summary',method:'get'})}
export function getIndicators(p){return request({url:'/nursing-quality/indicators',method:'get',params:p})}
export function collectIndicators(d){return request({url:'/nursing-quality/collect',method:'post',data:d})}

View File

@@ -17,6 +17,12 @@
</el-button>
<el-button
type="warning"
@click="handleCollect"
>
采集指标
</el-button>
<el-button
type="info"
@click="exportReport"
>
导出报告
@@ -324,7 +330,7 @@
<script setup>
import {ref, reactive, onMounted} from 'vue'
import {ElMessage} from 'element-plus'
import {getQualityPage, addIndicator, getQualitySummary} from './api'
import {getQualityPage, addIndicator, getQualitySummary, collectIndicators} from './api'
const loading = ref(false)
const indicatorData = ref([])
@@ -391,5 +397,15 @@ async function submitForm() {
function exportReport() { ElMessage.info('导出功能开发中') }
async function handleCollect() {
try {
const res = await collectIndicators({ departmentName: q.value.departmentName || '' })
ElMessage.success(`已采集 ${res.data?.created || 0} 项指标`)
loadData()
} catch (e) {
ElMessage.error('采集失败')
}
}
onMounted(() => loadData())
</script>