fix: 数据字典规范化 + 申请单API路径修复 + 合理用药API创建

后端:
- 修复 RationalDrugController 路径重复前缀 /healthlink-his → /api/v1
- 修复 AntibioticController 路径重复前缀
- 修复 RequestFormManageController /get-page → /page 路径匹配前端
- 新增 GET /{id} PUT /withdraw/{id} DELETE /delete/{id} 兼容接口
- 新增 IRequestFormManageAppService.getRequestFormById 方法

前端:
- 新增 src/api/rationaldrug.js (合理用药API)
- 新增 src/api/antibiotic.js (抗菌药物API)
- 10个模块硬编码下拉框改为 useDict() 数据字典:
  infectionenhanced: 感染预警级别、环境监测类型
  rationaldrug: 配伍禁忌严重程度
  labenhanced: 报告状态、预约状态
  cssd: 器械追溯状态、操作步骤、托盘类型
  followup: 投诉类型、投诉状态
  casignature: 签名文档类型
  specimenbarcode: 标本状态
  empienhanced: 性别
  fhircda: FHIR资源类型

数据库:
- 新增14个字典类型: infection_alert_level, environment_monitor_type,
  lab_report_status, exam_appointment_status, cssd_trace_status,
  cssd_process_step, cssd_tray_type, complaint_type, complaint_status,
  sign_document_type, specimen_status, fhir_resource_type,
  interaction_severity
This commit is contained in:
2026-06-07 16:00:49 +08:00
parent a1e77e0962
commit d5a75083fd
17 changed files with 175 additions and 32 deletions

View File

@@ -7,7 +7,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@Tag(name = "抗菌药物管控")
@RestController @RequestMapping("/healthlink-his/api/v1/antibiotic")
@RestController @RequestMapping("/api/v1/antibiotic")
public class AntibioticController {
@Autowired private IAntibioticAppService antibioticAppService;
@Operation(summary = "查询药品限制规则") @GetMapping("/rules/{drugCode}")

View File

@@ -26,7 +26,7 @@ import java.util.Map;
* @author system
*/
@RestController
@RequestMapping("/healthlink-his/api/v1/rational-drug")
@RequestMapping("/api/v1/rational-drug")
@AllArgsConstructor
@Slf4j
@Tag(name = "合理用药管理")

View File

@@ -79,4 +79,12 @@ public interface IRequestFormManageAppService {
* @return 结果
*/
R<?> withdrawRequestForm(Long requestFormId);
/**
* 根据ID获取申请单详情
*
* @param id 申请单ID
* @return 申请单详情
*/
RequestFormQueryDto getRequestFormById(Long id);
}

View File

@@ -828,4 +828,22 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
return R.ok("撤回成功");
}
@Override
public RequestFormQueryDto getRequestFormById(Long id) {
RequestForm form = iRequestFormService.getById(id);
if (form == null) {
return null;
}
RequestFormQueryDto dto = new RequestFormQueryDto();
dto.setRequestFormId(form.getId());
dto.setPrescriptionNo(form.getPrescriptionNo());
dto.setName(form.getName());
dto.setDescJson(form.getDescJson());
dto.setRequesterId(form.getRequesterId());
dto.setEncounterId(form.getEncounterId());
dto.setStatus(form.getStatus());
dto.setCreateTime(form.getCreateTime());
return dto;
}
}

View File

@@ -191,7 +191,7 @@ public class RequestFormManageController {
* 分页查询申请单
* @return 申请单
*/
@RequestMapping(value = "/get-page")
@RequestMapping(value = "/page")
public R<IPage<RequestFormPageDto>> getRequestFormPage(@RequestBody RequestFormDto requestFormDto) {
return R.ok(iRequestFormManageAppService.getRequestFormPage(requestFormDto));
}
@@ -235,3 +235,30 @@ public class RequestFormManageController {
}
}
}
// ==================== 门诊申请单管理兼容接口 ====================
/**
* 根据ID获取申请单详情
*/
@GetMapping(value = "/{id}")
public R<?> getDetail(@PathVariable Long id) {
return R.ok(iRequestFormManageAppService.getRequestFormById(id));
}
/**
* 撤回申请单(路径参数版本,兼容门诊申请单管理页面)
*/
@PutMapping(value = "/withdraw/{id}")
public R<?> withdrawById(@PathVariable Long id) {
return iRequestFormManageAppService.withdrawRequestForm(id);
}
/**
* 删除申请单(路径参数版本,兼容门诊申请单管理页面)
*/
@DeleteMapping(value = "/delete/{id}")
public R<?> deleteById(@PathVariable Long id) {
return iRequestFormManageAppService.deleteRequestForm(id);
}
}

View File

@@ -0,0 +1,22 @@
import request from '@/utils/request'
// ==================== 抗菌药物管控 ====================
export function getRules(drugCode) {
return request({ url: `/healthlink-his/api/v1/antibiotic/rules/${drugCode}`, method: 'get' })
}
export function checkRestriction(drugCode, doctorLevel) {
return request({ url: '/healthlink-his/api/v1/antibiotic/check-restriction', method: 'get', params: { drugCode, doctorLevel } })
}
export function requestApproval(data) {
return request({ url: '/healthlink-his/api/v1/antibiotic/approval', method: 'post', data })
}
export function approve(id, approverId, approverName, result) {
return request({ url: `/healthlink-his/api/v1/antibiotic/approval/${id}`, method: 'put', params: { approverId, approverName, result } })
}
export function getStatistics(startDate, endDate) {
return request({ url: '/healthlink-his/api/v1/antibiotic/statistics', method: 'get', params: { startDate, endDate } })
}

View File

@@ -0,0 +1,52 @@
import request from '@/utils/request'
// ==================== 处方审核 ====================
export function auditPrescription(data) {
return request({ url: '/healthlink-his/api/v1/rational-drug/audit', method: 'post', data })
}
export function batchAudit(prescriptionIds) {
return request({ url: '/healthlink-his/api/v1/rational-drug/batch-audit', method: 'post', data: prescriptionIds })
}
export function getAuditStatistics() {
return request({ url: '/healthlink-his/api/v1/rational-drug/statistics', method: 'get' })
}
export function getAuditTrend(startDate) {
return request({ url: '/healthlink-his/api/v1/rational-drug/trend', method: 'get', params: { startDate } })
}
export function getAuditLog(encounterId) {
return request({ url: `/healthlink-his/api/v1/rational-drug/audit-log/${encounterId}`, method: 'get' })
}
// ==================== 配伍禁忌 ====================
export function checkInteraction(drugCodes) {
return request({ url: '/healthlink-his/api/v1/rational-drug/check-interaction', method: 'post', data: drugCodes })
}
export function listInteractionRules(params) {
return request({ url: '/healthlink-his/api/v1/rational-drug/interaction-rules', method: 'get', params })
}
export function addInteractionRule(data) {
return request({ url: '/healthlink-his/api/v1/rational-drug/interaction-rules', method: 'post', data })
}
export function updateInteractionRule(data) {
return request({ url: '/healthlink-his/api/v1/rational-drug/interaction-rules', method: 'put', data })
}
export function delInteractionRule(id) {
return request({ url: `/healthlink-his/api/v1/rational-drug/interaction-rules/${id}`, method: 'delete' })
}
// ==================== 剂量规则 ====================
export function listDosageRules(params) {
return request({ url: '/healthlink-his/api/v1/rational-drug/dosage-rules', method: 'get', params })
}
export function checkDosage(drugCode, dosage, population) {
return request({ url: '/healthlink-his/api/v1/rational-drug/check-dosage', method: 'get', params: { drugCode, dosage, population } })
}

View File

@@ -3,9 +3,9 @@
<el-form :model="formData" label-width="100px">
<el-form-item label="文档类型">
<el-select v-model="formData.documentType" placeholder="选择文档类型">
<el-option label="电子病历" value="EMR" /><el-option label="处方" value="PRESCRIPTION" />
<el-option label="医嘱" value="ORDER" /><el-option label="会诊" value="CONSULTATION" />
<el-option label="手术同意书" value="CONSENT" /><el-option label="护理记录" value="NURSING" />
<el-option v-for="d in sign_document_type" :key="d.value" :label="d.label" :value="d.value" />
</el-select>
</el-form-item>
<el-form-item label="文档ID"><el-input v-model="formData.documentId" placeholder="文档ID" /></el-form-item>
@@ -22,10 +22,12 @@
</template>
<script setup>
import { useDict } from '@/utils/dict'
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import request from '@/utils/request'
const { sign_document_type } = useDict('sign_document_type')
const visible = ref(false)
const formData = ref({})

View File

@@ -17,9 +17,9 @@
<div style="margin-bottom:12px;display:flex;gap:8px">
<el-input v-model="trayQ.trayCode" placeholder="器械编码" clearable style="width:140px"/>
<el-select v-model="trayQ.status" placeholder="状态" clearable style="width:120px">
<el-option label="在用" value="IN_USE"/><el-option label="清洗中" value="WASHING"/>
<el-option label="灭菌中" value="STERILIZING"/><el-option label="储存中" value="STORED"/>
<el-option label="已发放" value="DISTRIBUTED"/>
<el-option v-for="d in cssd_trace_status" :key="d.value" :label="d.label" :value="d.value" />
</el-select>
<el-button type="primary" @click="loadTrays">查询</el-button>
<el-button type="success" @click="trayDialog=true">新增器械包</el-button>
@@ -52,10 +52,10 @@
<el-form-item label="器械编码"><el-input v-model="scanForm.trayCode" placeholder="扫码或输入编码" style="width:200px"/></el-form-item>
<el-form-item label="操作步骤">
<el-select v-model="scanForm.stepType" style="width:140px">
<el-option label="回收" value="RECYCLE"/><el-option label="清洗" value="WASH"/>
<el-option label="消毒" value="DISINFECT"/><el-option label="包装" value="PACK"/>
<el-option label="灭菌" value="STERILIZE"/><el-option label="储存" value="STORE"/>
<el-option label="发放" value="DISTRIBUTE"/>
<el-option v-for="d in cssd_process_step" :key="d.value" :label="d.label" :value="d.value" />
</el-select>
</el-form-item>
<el-form-item label="操作人"><el-input v-model="scanForm.operatorName" style="width:120px"/></el-form-item>
@@ -128,7 +128,7 @@
<el-form-item label="编码"><el-input v-model="trayForm.trayCode"/></el-form-item>
<el-form-item label="名称"><el-input v-model="trayForm.trayName"/></el-form-item>
<el-form-item label="类型">
<el-select v-model="trayForm.trayType"><el-option label="手术器械" value="OPERATION"/><el-option label="管腔器械" value="TUBE"/><el-option label="精密器械" value="PRECISION"/><el-option label="普通器械" value="COMMON"/></el-select>
<el-select v-model="trayForm.trayType"><el-option v-for="d in cssd_tray_type" :key="d.value" :label="d.label" :value="d.value" /></el-select>
</el-form-item>
<el-form-item label="来源科室"><el-input v-model="trayForm.departmentSource"/></el-form-item>
</el-form>
@@ -144,8 +144,10 @@
</div>
</template>
<script setup>
import { useDict } from '@/utils/dict'
import {ref,onMounted} from 'vue';import {ElMessage} from 'element-plus';
import {getTrayPage,addTray,scanTrace,getTraceHistory,getBatchPage,completeBatch,releaseBatch,getExpiryAlerts,getStats} from './api'
const { cssd_trace_status, cssd_process_step, cssd_tray_type } = useDict('cssd_trace_status', 'cssd_process_step', 'cssd_tray_type')
const activeTab=ref('tray')
const trayData=ref([]);const batchData=ref([]);const expiryData=ref([]);const stats=ref({})
const trayQ=ref({pageNo:1,pageSize:20,trayCode:'',status:''})

View File

@@ -38,7 +38,7 @@
<el-row :gutter="20">
<el-col :span="12"><el-form-item label="姓名"><el-input v-model="formData.patientName" /></el-form-item></el-col>
<el-col :span="12"><el-form-item label="性别">
<el-select v-model="formData.gender"><el-option label="男" value="M" /><el-option label="女" value="F" /></el-select>
<el-select v-model="formData.gender"><el-option v-for="d in sys_user_sex" :key="d.value" :label="d.label" :value="d.value" /></el-select>
</el-form-item></el-col>
<el-col :span="12"><el-form-item label="出生日期"><el-date-picker v-model="formData.birthDate" type="date" value-format="YYYY-MM-DD" /></el-form-item></el-col>
<el-col :span="12"><el-form-item label="身份证号"><el-input v-model="formData.idCardNo" /></el-form-item></el-col>
@@ -55,10 +55,12 @@
</template>
<script setup>
import { useDict } from '@/utils/dict'
import { ref, reactive, onMounted } from 'vue'
import { registerPerson, findByGlobalId, findByIdCard, getStatistics } from '../api'
import { ElMessage } from 'element-plus'
const { sys_user_sex } = useDict('sys_user_sex')
const stats = ref({})
const searchForm = reactive({ globalId: '', idCardNo: '' })
const patientData = ref(null)

View File

@@ -52,7 +52,7 @@
<el-dialog v-model="showAddFhir" title="新增FHIR资源" width="600px">
<el-form :model="fhirForm" label-width="100px">
<el-form-item label="资源类型"><el-select v-model="fhirForm.resourceType"><el-option v-for="t in ['Patient','Encounter','Observation','Condition','MedicationRequest']" :key="t" :label="t" :value="t"/></el-select></el-form-item>
<el-form-item label="资源类型"><el-select v-model="fhirForm.resourceType"><el-option v-for="d in fhir_resource_type" :key="d.value" :label="d.label" :value="d.value" /></el-select></el-form-item>
<el-form-item label="资源ID"><el-input v-model="fhirForm.resourceId"/></el-form-item>
<el-form-item label="JSON内容"><el-input v-model="fhirForm.resourceJson" type="textarea" :rows="8"/></el-form-item>
</el-form>
@@ -65,10 +65,12 @@
</template>
<script setup>
import { useDict } from '@/utils/dict'
import {ref,reactive,onMounted} from 'vue'
import {ElMessage} from 'element-plus'
import {getFhirPage,createFhirResource,getFhirTypeStats,getCdaPage,createCdaDocument,publishCdaDocument,getMappingPage,addMapping} from './api'
const { fhir_resource_type } = useDict('fhir_resource_type')
const tab=ref('fhir')
const fhirData=ref([]),cdaData=ref([]),mappingData=ref([])
const fhirStats=ref({})

View File

@@ -4,11 +4,11 @@
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap">
<el-input v-model="q.patientName" placeholder="患者姓名" clearable style="width:130px"/>
<el-select v-model="q.complaintType" placeholder="投诉类型" clearable style="width:120px">
<el-option label="服务态度" value="ATTITUDE"/><el-option label="医疗质量" value="QUALITY"/>
<el-option label="等候时间" value="WAITING"/><el-option label="其他" value="OTHER"/>
<el-option v-for="d in complaint_type" :key="d.value" :label="d.label" :value="d.value" />
</el-select>
<el-select v-model="q.status" placeholder="状态" clearable style="width:100px">
<el-option label="待处理" value="PENDING"/><el-option label="已处理" value="HANDLED"/><el-option label="已关闭" value="CLOSED"/>
<el-option v-for="d in complaint_status" :key="d.value" :label="d.label" :value="d.value" />
</el-select>
<el-button type="primary" @click="loadData">查询</el-button>
<el-button type="success" @click="showAdd">登记投诉</el-button>
@@ -49,7 +49,7 @@
<el-form :model="addForm" label-width="100px">
<el-form-item label="患者ID"><el-input-number v-model="addForm.patientId" :min="1"/></el-form-item>
<el-form-item label="患者姓名"><el-input v-model="addForm.patientName"/></el-form-item>
<el-form-item label="投诉类型"><el-select v-model="addForm.complaintType"><el-option label="服务态度" value="ATTITUDE"/><el-option label="医疗质量" value="QUALITY"/><el-option label="等候时间" value="WAITING"/><el-option label="其他" value="OTHER"/></el-select></el-form-item>
<el-form-item label="投诉类型"><el-select v-model="addForm.complaintType"><el-option v-for="d in complaint_type" :key="d.value" :label="d.label" :value="d.value" /></el-select></el-form-item>
<el-form-item label="投诉内容"><el-input v-model="addForm.complaintContent" type="textarea" placeholder="详细描述投诉内容"/></el-form-item>
<el-form-item label="科室"><el-input v-model="addForm.departmentName"/></el-form-item>
</el-form>
@@ -72,9 +72,11 @@
</div>
</template>
<script setup>
import { useDict } from '@/utils/dict'
import {ref,onMounted} from 'vue'
import {ElMessage} from 'element-plus'
import {getPage,add,handle,close,del} from './api'
const { complaint_type, complaint_status } = useDict('complaint_type', 'complaint_status')
const tableData=ref([]);const total=ref(0)
const q=ref({pageNo:1,pageSize:20,patientName:'',complaintType:'',status:''})
const addVisible=ref(false);const handleVisible=ref(false)

View File

@@ -125,7 +125,7 @@
<el-table-column prop="departmentName" label="科室" width="120" />
<el-table-column prop="monitorType" label="监测类型" width="100">
<template #default="{ row }">
{{ {1:'空气',2:'物表',3:'手',4:'消毒液',5:'无菌物品'}[row.monitorType] || row.monitorType }}
{{ environment_monitor_type.find(d => d.value === String(row.monitorType))?.label || row.monitorType }}
</template>
</el-table-column>
<el-table-column prop="monitorItem" label="监测项目" width="150" />
@@ -150,7 +150,7 @@
<el-form-item label="病例数"><el-input-number v-model="outbreakForm.caseCount" :min="1" /></el-form-item>
<el-form-item label="预警级别">
<el-select v-model="outbreakForm.warningLevel">
<el-option label="黄色" value="YELLOW" /><el-option label="红色" value="RED" />
<el-option v-for="d in infection_alert_level" :key="d.value" :label="d.label" :value="d.value" />
</el-select>
</el-form-item>
</el-form>
@@ -222,11 +222,13 @@
</template>
<script setup>
import { useDict } from '@/utils/dict'
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getOutbreakPage, addOutbreak, handleOutbreak, excludeOutbreak, getSurveillancePage, addSurveillance, getHandHygienePage, addHandHygiene, getHandHygieneStats, getMdrPage, addMdr, isolateMdr, releaseMdr, getEnvMonitorPage, addEnvMonitor } from './api'
const activeTab = ref('outbreak')
const { infection_alert_level, environment_monitor_type } = useDict('infection_alert_level', 'environment_monitor_type')
const loading = ref(false)
// Data

View File

@@ -4,8 +4,8 @@
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap">
<el-input v-model="q.patientName" placeholder="患者" clearable style="width:140px"/>
<el-select v-model="q.status" placeholder="状态" clearable style="width:120px">
<el-option label="已预约" value="APPOINTED"/><el-option label="已签到" value="CHECKED_IN"/>
<el-option label="检查中" value="EXAMINING"/><el-option label="已完成" value="COMPLETED"/>
<el-option v-for="d in exam_appointment_status" :key="d.value" :label="d.label" :value="d.value" />
</el-select>
<el-button type="primary" @click="loadData">查询</el-button>
<el-button type="success" @click="openAppoint">新建预约</el-button>
@@ -51,7 +51,9 @@
</div>
</template>
<script setup>
import { useDict } from '@/utils/dict'
import {ref,onMounted} from 'vue';import {ElMessage} from 'element-plus';import {getPage,appoint,checkin,startExam,complete,cancel} from './api'
const { exam_appointment_status } = useDict('exam_appointment_status')
const tableData=ref([]);const total=ref(0);const q=ref({pageNo:1,pageSize:20,patientName:'',status:''})
const dlgVisible=ref(false);const form=ref({patientId:null,encounterId:null,patientName:'',examName:'',appointDate:null,appointTime:'',room:''})
const loadData=async()=>{const r=await getPage(q.value);tableData.value=r.data?.records||[];total.value=r.data?.total||0}

View File

@@ -3,7 +3,7 @@
<div style="margin-bottom:16px"><span style="font-size:18px;font-weight:bold">影像图文报告</span></div>
<div style="margin-bottom:12px;display:flex;gap:8px">
<el-select v-model="q.status" placeholder="状态" clearable style="width:120px">
<el-option label="草稿" value="DRAFT"/><el-option label="已报告" value="REPORTED"/><el-option label="已审核" value="VERIFIED"/>
<el-option v-for="d in lab_report_status" :key="d.value" :label="d.label" :value="d.value" />
</el-select>
<el-button type="primary" @click="loadData">查询</el-button>
<el-button type="success" @click="openReport">新建报告</el-button>
@@ -47,7 +47,9 @@
</div>
</template>
<script setup>
import { useDict } from '@/utils/dict'
import {ref,onMounted} from 'vue';import {ElMessage,ElMessageBox} from 'element-plus';import {getReportPage,addReport,submitReport,verifyReport} from './api'
const { lab_report_status } = useDict('lab_report_status')
const tableData=ref([]);const total=ref(0);const q=ref({pageNo:1,pageSize:20,status:''})
const dlgVisible=ref(false);const form=ref({applyId:null,encounterId:null,patientName:'',examName:'',reportType:'',findings:'',impression:'',conclusion:'',reportDoctor:''})
const loadData=async()=>{const r=await getReportPage(q.value);tableData.value=r.data?.records||[];total.value=r.data?.total||0}

View File

@@ -131,10 +131,12 @@
</template>
<script setup name="InteractionRule" lang="ts">
import { useDict } from '@/utils/dict'
import { ref, reactive, toRefs, onMounted, computed } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { listInteractionRules, addInteractionRule, updateInteractionRule, delInteractionRule } from '@/api/rationaldrug'
const { interaction_severity } = useDict('interaction_severity')
const ruleList = ref([])
const loading = ref(false)
const total = ref(0)
@@ -146,11 +148,7 @@ const dialogTitle = ref('')
const queryForm = ref(null)
const ruleForm = ref(null)
const severityOptions = [
{ label: '严重', value: '严重' },
{ label: '中度', value: '中度' },
{ label: '轻度', value: '轻度' }
]
const severityOptions = computed(() => interaction_severity.value || [])
const data = reactive({
queryParams: {

View File

@@ -5,8 +5,8 @@
<el-input v-model="q.patientName" placeholder="患者姓名" clearable style="width:140px"/>
<el-input v-model="q.barcode" placeholder="条码号" clearable style="width:140px"/>
<el-select v-model="q.status" placeholder="状态" clearable style="width:120px">
<el-option label="已采集" value="COLLECTED"/>
<el-option label="已扫码" value="SCANNED"/>
<el-option v-for="d in specimen_status" :key="d.value" :label="d.label" :value="d.value" />
<el-option label="已拒收" value="REJECTED"/>
</el-select>
<el-input v-model="scanInput" placeholder="扫码(回车确认)" style="width:180px" @keyup.enter="doScan"/>
@@ -58,9 +58,11 @@
</div>
</template>
<script setup>
import { useDict } from '@/utils/dict'
import {ref,onMounted} from 'vue'
import {ElMessage,ElMessageBox} from 'element-plus'
import {getPage,add,update,scanConfirm,reject,del} from './api'
const { specimen_status } = useDict('specimen_status')
const tableData=ref([]);const total=ref(0)
const q=ref({pageNo:1,pageSize:20,patientName:'',barcode:'',status:''})
const scanInput=ref('')