feat(anesthesia): 术后随访记录

This commit is contained in:
2026-06-17 13:00:50 +08:00
parent b3800b7ae0
commit 694935b648
5 changed files with 329 additions and 0 deletions

View File

@@ -5,6 +5,7 @@ import com.healthlink.his.anesthesia.domain.AnesSummary;
import com.healthlink.his.anesthesia.domain.AnesthesiaFollowup;
import com.healthlink.his.anesthesia.domain.AnesthesiaIoRecord;
import com.healthlink.his.anesthesia.domain.AnesthesiaMedication;
import com.healthlink.his.anesthesia.domain.AnesthesiaPostopFollowup;
import com.healthlink.his.anesthesia.domain.AnesthesiaRecord;
import com.healthlink.his.anesthesia.domain.AnesthesiaVitalSign;
import com.healthlink.his.anesthesia.dto.AnesthesiaIoSummaryDto;
@@ -45,4 +46,8 @@ public interface IAnesthesiaAppService {
AnesSummary saveSummary(AnesSummary summary);
AnesSummary getSummary(Long recordId);
AnesthesiaPostopFollowup recordFollowup(AnesthesiaPostopFollowup followup);
List<AnesthesiaPostopFollowup> getFollowups(Long encounterId);
}

View File

@@ -5,6 +5,7 @@ import com.healthlink.his.anesthesia.domain.AnesSummary;
import com.healthlink.his.anesthesia.domain.AnesthesiaFollowup;
import com.healthlink.his.anesthesia.domain.AnesthesiaIoRecord;
import com.healthlink.his.anesthesia.domain.AnesthesiaMedication;
import com.healthlink.his.anesthesia.domain.AnesthesiaPostopFollowup;
import com.healthlink.his.anesthesia.domain.AnesthesiaRecord;
import com.healthlink.his.anesthesia.domain.AnesthesiaVitalSign;
import com.healthlink.his.anesthesia.dto.AnesthesiaIoSummaryDto;
@@ -13,10 +14,12 @@ import com.healthlink.his.anesthesia.service.IAnesAsaAssessmentService;
import com.healthlink.his.anesthesia.service.IAnesSummaryService;
import com.healthlink.his.anesthesia.service.IAnesthesiaFollowupService;
import com.healthlink.his.anesthesia.service.IAnesthesiaIoRecordService;
import com.healthlink.his.anesthesia.service.IAnesthesiaPostopFollowupService;
import com.healthlink.his.anesthesia.service.IAnesthesiaMedicationService;
import com.healthlink.his.anesthesia.service.IAnesthesiaRecordService;
import com.healthlink.his.anesthesia.service.IAnesthesiaVitalSignService;
import com.healthlink.his.web.anesthesia.appservice.IAnesthesiaAppService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -49,6 +52,9 @@ public class AnesthesiaAppServiceImpl implements IAnesthesiaAppService {
@Resource
private IAnesSummaryService anesSummaryService;
@Resource
private IAnesthesiaPostopFollowupService anesthesiaPostopFollowupService;
@Override
@Transactional
public AnesthesiaRecord createRecord(AnesthesiaRecord record) {
@@ -185,4 +191,24 @@ public class AnesthesiaAppServiceImpl implements IAnesthesiaAppService {
public AnesSummary getSummary(Long recordId) {
return anesSummaryService.selectByRecordId(recordId);
}
@Override
@Transactional(rollbackFor = Exception.class)
public AnesthesiaPostopFollowup recordFollowup(AnesthesiaPostopFollowup followup) {
if (followup.getId() != null) {
anesthesiaPostopFollowupService.updateById(followup);
} else {
followup.setStatus(0);
anesthesiaPostopFollowupService.save(followup);
}
return followup;
}
@Override
public List<AnesthesiaPostopFollowup> getFollowups(Long encounterId) {
LambdaQueryWrapper<AnesthesiaPostopFollowup> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(AnesthesiaPostopFollowup::getEncounterId, encounterId)
.orderByDesc(AnesthesiaPostopFollowup::getFollowupTime);
return anesthesiaPostopFollowupService.list(wrapper);
}
}

View File

@@ -6,6 +6,7 @@ import com.healthlink.his.anesthesia.domain.AnesSummary;
import com.healthlink.his.anesthesia.domain.AnesthesiaFollowup;
import com.healthlink.his.anesthesia.domain.AnesthesiaIoRecord;
import com.healthlink.his.anesthesia.domain.AnesthesiaMedication;
import com.healthlink.his.anesthesia.domain.AnesthesiaPostopFollowup;
import com.healthlink.his.anesthesia.domain.AnesthesiaRecord;
import com.healthlink.his.anesthesia.domain.AnesthesiaVitalSign;
import com.healthlink.his.anesthesia.dto.AnesthesiaIoSummaryDto;
@@ -179,4 +180,18 @@ public class AnesthesiaController {
public R<AnesSummary> getSummary(@PathVariable Long recordId) {
return R.ok(anesthesiaAppService.getSummary(recordId));
}
@PostMapping("/postop-followup")
@Operation(summary = "记录术后随访")
@PreAuthorize("@ss.hasPermi('inpatient:anesthesia:edit')")
public R<AnesthesiaPostopFollowup> recordFollowup(@RequestBody AnesthesiaPostopFollowup followup) {
return R.ok(anesthesiaAppService.recordFollowup(followup));
}
@GetMapping("/postop-followup/{encounterId}")
@Operation(summary = "查询术后随访列表")
@PreAuthorize("@ss.hasPermi('inpatient:anesthesia:list')")
public R<List<AnesthesiaPostopFollowup>> getPostopFollowups(@PathVariable Long encounterId) {
return R.ok(anesthesiaAppService.getFollowups(encounterId));
}
}

View File

@@ -7,3 +7,5 @@ export function getVitalSigns(recordId) { return request({ url: '/api/v1/anesthe
export function getMedications(recordId) { return request({ url: '/api/v1/anesthesia/medication/' + recordId, method: 'get' }) }
export function getIoSummary(recordId) { return request({ url: '/api/v1/anesthesia/io-summary/' + recordId, method: 'get' }) }
export function completeRecord(id) { return request({ url: '/api/v1/anesthesia/complete/' + id, method: 'put' }) }
export function recordPostopFollowup(data) { return request({ url: '/api/v1/anesthesia/postop-followup', method: 'post', data }) }
export function getPostopFollowups(encounterId) { return request({ url: '/api/v1/anesthesia/postop-followup/' + encounterId, method: 'get' }) }

View File

@@ -0,0 +1,281 @@
<template>
<div class="postop-followup-container">
<el-card v-loading="loading">
<template #header>
<div class="card-header">
<span>术后随访记录</span>
<el-button type="primary" size="small" @click="handleNew">新建随访</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="followupType" label="随访类型" width="100" align="center">
<template #default="scope">
<el-tag :type="getTypeTag(scope.row.followupType)">{{ scope.row.followupType }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="followupTime" label="随访时间" width="170" />
<el-table-column prop="painScore" label="疼痛评分" width="90" align="center">
<template #default="scope">
<el-tag :type="getPainType(scope.row.painScore)">{{ scope.row.painScore ?? '-' }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="nauseaVomiting" label="恶心呕吐" width="90" align="center">
<template #default="scope">
<el-tag v-if="scope.row.nauseaVomiting" type="danger"></el-tag>
<el-tag v-else type="success"></el-tag>
</template>
</el-table-column>
<el-table-column prop="consciousnessStatus" label="意识状态" width="100" />
<el-table-column prop="vitalSigns" label="生命体征" min-width="140" show-overflow-tooltip />
<el-table-column prop="complications" label="并发症" min-width="140" show-overflow-tooltip />
<el-table-column prop="treatment" label="处理措施" min-width="140" show-overflow-tooltip />
<el-table-column prop="followupDoctorName" label="随访医师" width="100" />
<el-table-column label="操作" width="80" align="center" fixed="right">
<template #default="scope">
<el-button link type="primary" @click="handleEdit(scope.row)">编辑</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<el-dialog v-model="dialogVisible" :title="isEdit ? '编辑术后随访' : '新建术后随访'" width="750px" destroy-on-close :close-on-click-modal="false">
<el-form ref="formRef" :model="form" :rules="rules" label-width="110px">
<el-divider content-position="left">基本信息</el-divider>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="随访类型" prop="followupType">
<el-select v-model="form.followupType" placeholder="请选择随访类型" style="width: 100%">
<el-option label="24h随访" value="24h" />
<el-option label="48h随访" value="48h" />
<el-option label="72h随访" value="72h" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="随访时间" prop="followupTime">
<el-date-picker v-model="form.followupTime" 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="就诊ID" prop="encounterId">
<el-input-number v-model="form.encounterId" :min="1" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="患者ID" prop="patientId">
<el-input-number v-model="form.patientId" :min="1" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="患者姓名">
<el-input v-model="form.patientName" placeholder="请输入患者姓名" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="手术ID">
<el-input-number v-model="form.surgeryId" :min="0" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-divider content-position="left">疼痛评估</el-divider>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="疼痛评分" prop="painScore">
<el-input-number v-model="form.painScore" :min="0" :max="10" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="恶心呕吐">
<el-select v-model="form.nauseaVomiting" style="width: 100%">
<el-option label="无" :value="false" />
<el-option label="有" :value="true" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-divider content-position="left">术后评估</el-divider>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="意识状态">
<el-select v-model="form.consciousnessStatus" placeholder="请选择" style="width: 100%">
<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="随访医师ID">
<el-input-number v-model="form.followupDoctorId" :min="0" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="随访医师姓名">
<el-input v-model="form.followupDoctorName" placeholder="请输入医师姓名" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="生命体征">
<el-input v-model="form.vitalSigns" type="textarea" :rows="2" placeholder="请描述生命体征T 36.5℃, P 78次/min, R 18次/min, BP 120/80mmHg" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="并发症">
<el-input v-model="form.complications" type="textarea" :rows="2" placeholder="请描述并发症情况" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="处理措施">
<el-input v-model="form.treatment" type="textarea" :rows="2" placeholder="请输入处理措施" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" :loading="submitLoading" @click="handleSave">保存</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { recordPostopFollowup, getPostopFollowups } from '@/api/anesthesia'
const props = defineProps({
encounterId: { type: [Number, String], default: null }
})
const loading = ref(false)
const submitLoading = ref(false)
const tableData = ref([])
const dialogVisible = ref(false)
const isEdit = ref(false)
const formRef = ref(null)
const form = reactive({
id: null,
encounterId: null,
patientId: null,
patientName: '',
surgeryId: null,
followupType: '',
followupTime: '',
painScore: null,
nauseaVomiting: false,
consciousnessStatus: '',
vitalSigns: '',
complications: '',
treatment: '',
followupDoctorId: null,
followupDoctorName: ''
})
const rules = {
followupType: [{ required: true, message: '请选择随访类型', trigger: 'change' }],
followupTime: [{ required: true, message: '请选择随访时间', trigger: 'change' }],
encounterId: [{ required: true, message: '请输入就诊ID', trigger: 'blur' }],
patientId: [{ required: true, message: '请输入患者ID', trigger: 'blur' }],
painScore: [{ required: true, message: '请输入疼痛评分', trigger: 'blur' }]
}
function getTypeTag(type) {
const map = { '24h': 'success', '48h': 'warning', '72h': 'danger' }
return map[type] || 'info'
}
function getPainType(score) {
if (score == null) return 'info'
if (score <= 3) return 'success'
if (score <= 6) return 'warning'
return 'danger'
}
function resetForm() {
form.id = null
form.encounterId = null
form.patientId = null
form.patientName = ''
form.surgeryId = null
form.followupType = ''
form.followupTime = ''
form.painScore = null
form.nauseaVomiting = false
form.consciousnessStatus = ''
form.vitalSigns = ''
form.complications = ''
form.treatment = ''
form.followupDoctorId = null
form.followupDoctorName = ''
}
function handleNew() {
resetForm()
isEdit.value = false
dialogVisible.value = true
}
function handleEdit(row) {
Object.assign(form, row)
isEdit.value = true
dialogVisible.value = true
}
function handleSave() {
formRef.value?.validate(valid => {
if (!valid) return
submitLoading.value = true
recordPostopFollowup({ ...form }).then(res => {
if (res.code === 200) {
ElMessage.success('保存成功')
dialogVisible.value = false
loadData()
} else {
ElMessage.error(res.msg || '保存失败')
}
}).catch(() => {
ElMessage.error('保存失败')
}).finally(() => {
submitLoading.value = false
})
})
}
function loadData() {
if (!props.encounterId) return
loading.value = true
getPostopFollowups(props.encounterId).then(res => {
tableData.value = res.data || []
}).catch(() => {
ElMessage.error('查询失败')
}).finally(() => {
loading.value = false
})
}
watch(() => props.encounterId, () => {
loadData()
})
onMounted(() => {
loadData()
})
</script>
<style scoped>
.postop-followup-container {
padding: 12px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>