feat(clinical-pathway): 临床路径执行管理

This commit is contained in:
2026-06-17 12:12:41 +08:00
parent 5ef05b9b55
commit 815b80437e
10 changed files with 803 additions and 1 deletions

View File

@@ -0,0 +1,17 @@
package com.healthlink.his.web.clinical.appservice;
import com.healthlink.his.clinical.domain.ClinicalPathway;
import com.healthlink.his.clinical.domain.ClinicalPathwayExecution;
import com.healthlink.his.clinical.domain.ClinicalPathwayVariance;
import com.baomidou.mybatisplus.core.metadata.IPage;
import java.util.Map;
public interface IClinicalPathwayAppService {
Map<String, Object> evaluate(Long encounterId);
void admit(ClinicalPathwayExecution execution);
void recordExecution(ClinicalPathwayExecution execution);
void recordVariance(ClinicalPathwayVariance variance);
void discharge(Long executionId);
Map<String, Object> getStatistics(String startDate, String endDate);
IPage<ClinicalPathway> page(String diseaseCode, Integer pageNo, Integer pageSize);
void add(ClinicalPathway pathway);
IPage<Map<String, Object>> getExecutionPage(Long pathwayId, Integer pageNo, Integer pageSize);
}

View File

@@ -0,0 +1,178 @@
package com.healthlink.his.web.clinical.appservice.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.healthlink.his.clinical.domain.ClinicalPathway;
import com.healthlink.his.clinical.domain.ClinicalPathwayExecution;
import com.healthlink.his.clinical.domain.ClinicalPathwayVariance;
import com.healthlink.his.clinical.service.IClinicalPathwayService;
import com.healthlink.his.clinical.service.IClinicalPathwayExecutionService;
import com.healthlink.his.clinical.service.IClinicalPathwayVarianceService;
import com.healthlink.his.web.clinical.appservice.IClinicalPathwayAppService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.*;
@Service
public class ClinicalPathwayAppServiceImpl implements IClinicalPathwayAppService {
@Autowired
private IClinicalPathwayService pathwayService;
@Autowired
private IClinicalPathwayExecutionService executionService;
@Autowired
private IClinicalPathwayVarianceService varianceService;
@Override
public Map<String, Object> evaluate(Long encounterId) {
Map<String, Object> result = new LinkedHashMap<>();
LambdaQueryWrapper<ClinicalPathwayExecution> qw = new LambdaQueryWrapper<>();
qw.eq(ClinicalPathwayExecution::getEncounterId, encounterId)
.eq(ClinicalPathwayExecution::getStatus, "IN_PATH");
ClinicalPathwayExecution active = executionService.getOne(qw);
result.put("hasActivePathway", active != null);
if (active != null) {
result.put("activeExecution", active);
LambdaQueryWrapper<ClinicalPathway> pw = new LambdaQueryWrapper<>();
pw.eq(ClinicalPathway::getId, active.getPathwayId());
result.put("pathway", pathwayService.getOne(pw));
}
LambdaQueryWrapper<ClinicalPathwayExecution> allQw = new LambdaQueryWrapper<>();
allQw.eq(ClinicalPathwayExecution::getEncounterId, encounterId);
result.put("totalExecutions", executionService.count(allQw));
return result;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void admit(ClinicalPathwayExecution execution) {
execution.setStatus("IN_PATH");
execution.setEnterDate(LocalDate.now());
execution.setCreateTime(new Date());
executionService.save(execution);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void recordExecution(ClinicalPathwayExecution execution) {
LambdaQueryWrapper<ClinicalPathwayExecution> qw = new LambdaQueryWrapper<>();
qw.eq(ClinicalPathwayExecution::getEncounterId, execution.getEncounterId())
.eq(ClinicalPathwayExecution::getStatus, "IN_PATH");
ClinicalPathwayExecution existing = executionService.getOne(qw);
if (existing == null) {
throw new RuntimeException("当前无在径执行记录");
}
if (execution.getActualDays() != null) {
existing.setActualDays(execution.getActualDays());
}
if (execution.getActualCost() != null) {
existing.setActualCost(execution.getActualCost());
}
existing.setUpdateTime(new Date());
executionService.updateById(existing);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void recordVariance(ClinicalPathwayVariance variance) {
variance.setCreateTime(new Date());
varianceService.save(variance);
LambdaQueryWrapper<ClinicalPathwayExecution> qw = new LambdaQueryWrapper<>();
qw.eq(ClinicalPathwayExecution::getId, variance.getExecutionId());
ClinicalPathwayExecution exec = executionService.getOne(qw);
if (exec != null) {
exec.setStatus("VARIATION");
exec.setVariationReason(variance.getVarianceReason());
exec.setUpdateTime(new Date());
executionService.updateById(exec);
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void discharge(Long executionId) {
ClinicalPathwayExecution exec = executionService.getById(executionId);
if (exec == null) {
throw new RuntimeException("执行记录不存在");
}
exec.setStatus("COMPLETED");
exec.setCompleteDate(LocalDate.now());
if (exec.getEnterDate() != null) {
long days = ChronoUnit.DAYS.between(exec.getEnterDate(), LocalDate.now());
exec.setActualDays((int) days);
}
exec.setUpdateTime(new Date());
executionService.updateById(exec);
}
@Override
public Map<String, Object> getStatistics(String startDate, String endDate) {
Map<String, Object> stats = new LinkedHashMap<>();
stats.put("totalPathways", pathwayService.count());
stats.put("totalExecutions", executionService.count());
LambdaQueryWrapper<ClinicalPathwayExecution> inPathQw = new LambdaQueryWrapper<>();
inPathQw.eq(ClinicalPathwayExecution::getStatus, "IN_PATH");
stats.put("inPathCount", executionService.count(inPathQw));
LambdaQueryWrapper<ClinicalPathwayExecution> completedQw = new LambdaQueryWrapper<>();
completedQw.eq(ClinicalPathwayExecution::getStatus, "COMPLETED");
stats.put("completedCount", executionService.count(completedQw));
LambdaQueryWrapper<ClinicalPathwayExecution> variationQw = new LambdaQueryWrapper<>();
variationQw.eq(ClinicalPathwayExecution::getStatus, "VARIATION");
stats.put("variationCount", executionService.count(variationQw));
LambdaQueryWrapper<ClinicalPathwayExecution> exitQw = new LambdaQueryWrapper<>();
exitQw.eq(ClinicalPathwayExecution::getStatus, "EXIT");
stats.put("exitCount", executionService.count(exitQw));
long total = executionService.count();
long completed = (long) stats.get("completedCount");
stats.put("completionRate", total > 0 ? Math.round(completed * 100.0 / total) : 0);
stats.put("totalVariances", varianceService.count());
return stats;
}
@Override
public IPage<ClinicalPathway> page(String diseaseCode, Integer pageNo, Integer pageSize) {
LambdaQueryWrapper<ClinicalPathway> w = new LambdaQueryWrapper<>();
w.eq(StringUtils.hasText(diseaseCode), ClinicalPathway::getDiseaseCode, diseaseCode)
.eq(ClinicalPathway::getStatus, "ACTIVE");
return pathwayService.page(new Page<>(pageNo, pageSize), w);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void add(ClinicalPathway pathway) {
pathway.setVersion(1);
pathway.setStatus("ACTIVE");
pathway.setCreateTime(new Date());
pathwayService.save(pathway);
}
@Override
public IPage<Map<String, Object>> getExecutionPage(Long pathwayId, Integer pageNo, Integer pageSize) {
LambdaQueryWrapper<ClinicalPathwayExecution> qw = new LambdaQueryWrapper<>();
qw.eq(pathwayId != null, ClinicalPathwayExecution::getPathwayId, pathwayId)
.orderByDesc(ClinicalPathwayExecution::getCreateTime);
Page<ClinicalPathwayExecution> execPage = executionService.page(new Page<>(pageNo, pageSize), qw);
Page<Map<String, Object>> result = new Page<>(execPage.getCurrent(), execPage.getSize(), execPage.getTotal());
List<Map<String, Object>> records = new ArrayList<>();
for (ClinicalPathwayExecution exec : execPage.getRecords()) {
Map<String, Object> row = new LinkedHashMap<>();
row.put("id", exec.getId());
row.put("pathwayId", exec.getPathwayId());
row.put("encounterId", exec.getEncounterId());
row.put("patientId", exec.getPatientId());
row.put("patientName", exec.getPatientName());
row.put("enterDate", exec.getEnterDate());
row.put("expectedDays", exec.getExpectedDays());
row.put("actualDays", exec.getActualDays());
row.put("expectedCost", exec.getExpectedCost());
row.put("actualCost", exec.getActualCost());
row.put("status", exec.getStatus());
row.put("completeDate", exec.getCompleteDate());
row.put("createTime", exec.getCreateTime());
LambdaQueryWrapper<ClinicalPathway> pw = new LambdaQueryWrapper<>();
pw.eq(ClinicalPathway::getId, exec.getPathwayId());
ClinicalPathway pwObj = pathwayService.getOne(pw);
if (pwObj != null) {
row.put("pathwayName", pwObj.getPathwayName());
row.put("diseaseCode", pwObj.getDiseaseCode());
row.put("diseaseName", pwObj.getDiseaseName());
}
records.add(row);
}
result.setRecords(records);
return result;
}
}

View File

@@ -1,17 +1,23 @@
package com.healthlink.his.web.clinical.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.core.common.core.domain.AjaxResult;
import com.core.common.core.domain.R;
import com.healthlink.his.clinical.domain.*;
import com.healthlink.his.clinical.service.*;
import com.healthlink.his.web.clinical.appservice.IClinicalPathwayAppService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController @RequestMapping("/clinical-pathway") @Slf4j @AllArgsConstructor
@Tag(name = "临床路径管理") @RestController @RequestMapping("/clinical-pathway") @Slf4j @AllArgsConstructor
public class ClinicalPathwayController {
private final IClinicalPathwayService pathwayService;
private final IClinicalPathwayExecutionService executionService;
private final IClinicalPathwayAppService clinicalPathwayAppService;
@GetMapping("/page")
public R<?> getPage(@RequestParam(value="diseaseCode",required=false) String diseaseCode,
@RequestParam(value="pageNo",defaultValue="1") Integer pageNo,
@@ -59,4 +65,45 @@ public class ClinicalPathwayController {
stats.put("completionRate", total > 0 ? Math.round(completed * 100.0 / total) : 0);
return R.ok(stats);
}
@Operation(summary = "入径评估")
@PreAuthorize("@ss.hasPermi('inpatient:clinical:list')")
@GetMapping("/evaluate/{encounterId}")
public AjaxResult evaluate(@PathVariable Long encounterId) {
return AjaxResult.success(clinicalPathwayAppService.evaluate(encounterId));
}
@Operation(summary = "入径")
@PreAuthorize("@ss.hasPermi('inpatient:clinical:edit')")
@PostMapping("/admission")
public AjaxResult admission(@RequestBody ClinicalPathwayExecution execution) {
clinicalPathwayAppService.admit(execution);
return AjaxResult.success();
}
@Operation(summary = "执行记录")
@PreAuthorize("@ss.hasPermi('inpatient:clinical:edit')")
@PostMapping("/execution/record")
public AjaxResult executionRecord(@RequestBody ClinicalPathwayExecution execution) {
clinicalPathwayAppService.recordExecution(execution);
return AjaxResult.success();
}
@Operation(summary = "变异记录")
@PreAuthorize("@ss.hasPermi('inpatient:clinical:edit')")
@PostMapping("/variance/record")
public AjaxResult varianceRecord(@RequestBody ClinicalPathwayVariance variance) {
clinicalPathwayAppService.recordVariance(variance);
return AjaxResult.success();
}
@Operation(summary = "出径")
@PreAuthorize("@ss.hasPermi('inpatient:clinical:edit')")
@PostMapping("/discharge")
public AjaxResult discharge(@RequestParam Long executionId) {
clinicalPathwayAppService.discharge(executionId);
return AjaxResult.success();
}
@Operation(summary = "路径统计")
@PreAuthorize("@ss.hasPermi('inpatient:clinical:list')")
@GetMapping("/statistics")
public AjaxResult statistics(@RequestParam(required = false) String startDate,
@RequestParam(required = false) String endDate) {
return AjaxResult.success(clinicalPathwayAppService.getStatistics(startDate, endDate));
}
}

View File

@@ -0,0 +1,21 @@
CREATE TABLE IF NOT EXISTS clinical_pathway_variance (
id BIGSERIAL PRIMARY KEY,
execution_id BIGINT NOT NULL,
pathway_id BIGINT NOT NULL,
variance_date DATE NOT NULL,
variance_type VARCHAR(20) NOT NULL,
expected_item VARCHAR(200),
actual_item VARCHAR(200),
variance_reason TEXT,
adjustment_action TEXT,
record_by BIGINT,
record_by_name VARCHAR(50),
tenant_id INTEGER DEFAULT 0,
del_flag CHAR(1) DEFAULT '0',
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
create_by VARCHAR(64),
update_time TIMESTAMP,
update_by VARCHAR(64)
);
CREATE INDEX IF NOT EXISTS idx_cpv_execution ON clinical_pathway_variance(execution_id);
CREATE INDEX IF NOT EXISTS idx_cpv_pathway ON clinical_pathway_variance(pathway_id);

View File

@@ -0,0 +1,12 @@
package com.healthlink.his.clinical.domain;
import com.baomidou.mybatisplus.annotation.*;import com.core.common.core.domain.HisBaseEntity;
import lombok.Data;import lombok.EqualsAndHashCode;
import java.time.LocalDate;
@Data @EqualsAndHashCode(callSuper=true) @TableName("clinical_pathway_variance")
public class ClinicalPathwayVariance extends HisBaseEntity {
@TableId(value="id",type=IdType.ASSIGN_ID) private Long id;
private Long executionId; private Long pathwayId; private LocalDate varianceDate;
private String varianceType; private String expectedItem; private String actualItem;
private String varianceReason; private String adjustmentAction;
private Long recordBy; private String recordByName;
}

View File

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

View File

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

View File

@@ -0,0 +1,8 @@
package com.healthlink.his.clinical.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.clinical.domain.ClinicalPathwayVariance;
import com.healthlink.his.clinical.mapper.ClinicalPathwayVarianceMapper;
import com.healthlink.his.clinical.service.IClinicalPathwayVarianceService;
import org.springframework.stereotype.Service;
@Service
public class ClinicalPathwayVarianceServiceImpl extends ServiceImpl<ClinicalPathwayVarianceMapper, ClinicalPathwayVariance> implements IClinicalPathwayVarianceService {}

View File

@@ -0,0 +1,37 @@
import request from '@/utils/request'
export function evaluatePathway(encounterId) {
return request({ url: '/clinical-pathway/evaluate/' + encounterId, method: 'get' })
}
export function admitPathway(data) {
return request({ url: '/clinical-pathway/admission', method: 'post', data: data })
}
export function recordExecution(data) {
return request({ url: '/clinical-pathway/execution/record', method: 'post', data: data })
}
export function recordVariance(data) {
return request({ url: '/clinical-pathway/variance/record', method: 'post', data: data })
}
export function dischargePathway(executionId) {
return request({ url: '/clinical-pathway/discharge', method: 'post', params: { executionId: executionId } })
}
export function getPathwayStatistics(params) {
return request({ url: '/clinical-pathway/statistics', method: 'get', params: params })
}
export function getPathwayPage(params) {
return request({ url: '/clinical-pathway/page', method: 'get', params: params })
}
export function addPathway(data) {
return request({ url: '/clinical-pathway/add', method: 'post', data: data })
}
export function getExecutionPage(params) {
return request({ url: '/clinical-pathway/execution/page', method: 'get', params: params })
}

View File

@@ -0,0 +1,472 @@
<template>
<div class="clinical-pathway-container">
<el-row :gutter="16" class="stats-row">
<el-col :span="4"><el-card shadow="hover"><el-statistic title="路径总数" :value="stats.totalPathways || 0" /></el-card></el-col>
<el-col :span="4"><el-card shadow="hover"><el-statistic title="在径数" :value="stats.inPathCount || 0" /></el-card></el-col>
<el-col :span="4"><el-card shadow="hover"><el-statistic title="完成数" :value="stats.completedCount || 0" /></el-card></el-col>
<el-col :span="4"><el-card shadow="hover"><el-statistic title="变异数" :value="stats.variationCount || 0" /></el-card></el-col>
<el-col :span="4"><el-card shadow="hover"><el-statistic title="退出数" :value="stats.exitCount || 0" /></el-card></el-col>
<el-col :span="4"><el-card shadow="hover"><el-statistic title="完成率(%)" :value="stats.completionRate || 0" /></el-card></el-col>
</el-row>
<el-card>
<template #header>
<div class="card-header">
<span>临床路径管理</span>
<el-button type="primary" size="small" @click="handleAddPathway">新增路径</el-button>
</div>
</template>
<el-form :inline="true" :model="pathwayQuery" class="query-form">
<el-form-item label="疾病编码">
<el-input v-model="pathwayQuery.diseaseCode" placeholder="疾病编码" clearable style="width: 160px" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadPathways">查询</el-button>
<el-button @click="pathwayQuery.diseaseCode = ''; loadPathways()">重置</el-button>
</el-form-item>
</el-form>
<el-table :data="pathwayList" border style="width: 100%">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="diseaseCode" label="疾病编码" width="120" />
<el-table-column prop="diseaseName" label="疾病名称" width="140" />
<el-table-column prop="pathwayName" label="路径名称" min-width="200" show-overflow-tooltip />
<el-table-column prop="departmentName" label="科室" width="120" />
<el-table-column prop="avgDays" label="平均住院日" width="100" align="center" />
<el-table-column prop="version" label="版本" width="70" align="center" />
<el-table-column prop="status" label="状态" width="80" align="center">
<template #default="scope">
<el-tag :type="scope.row.status === 'ACTIVE' ? 'success' : 'info'">{{ scope.row.status === 'ACTIVE' ? '启用' : '停用' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="240" align="center" fixed="right">
<template #default="scope">
<el-button link type="primary" @click="handleEnterPathway(scope.row)">入径</el-button>
<el-button link type="success" @click="handleViewExecution(scope.row)">执行</el-button>
<el-button link type="warning" @click="handleVariance(scope.row)">变异</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
v-model:current-page="pathwayQuery.pageNo"
v-model:page-size="pathwayQuery.pageSize"
:page-sizes="[10, 20, 50]"
layout="total, sizes, prev, pager, next, jumper"
:total="pathwayTotal"
style="margin-top: 12px; justify-content: flex-end"
@size-change="loadPathways"
@current-change="loadPathways"
/>
</el-card>
<el-dialog v-model="addDialogVisible" title="新增临床路径" width="650px" destroy-on-close :close-on-click-modal="false">
<el-form ref="addFormRef" :model="addForm" :rules="addRules" label-width="110px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="疾病编码" prop="diseaseCode">
<el-input v-model="addForm.diseaseCode" placeholder="请输入疾病编码" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="疾病名称" prop="diseaseName">
<el-input v-model="addForm.diseaseName" placeholder="请输入疾病名称" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="路径名称" prop="pathwayName">
<el-input v-model="addForm.pathwayName" placeholder="请输入路径名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="科室">
<el-input v-model="addForm.departmentName" placeholder="请输入科室" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="平均住院日">
<el-input-number v-model="addForm.avgDays" :min="1" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="路径内容">
<el-input v-model="addForm.pathwayJson" type="textarea" :rows="4" placeholder="请输入路径内容JSON格式" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<el-button @click="addDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="submitLoading" @click="submitAddPathway">确定</el-button>
</template>
</el-dialog>
<el-dialog v-model="enterDialogVisible" title="入径" width="600px" destroy-on-close :close-on-click-modal="false">
<el-form ref="enterFormRef" :model="enterForm" :rules="enterRules" label-width="110px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="就诊ID" prop="encounterId">
<el-input v-model="enterForm.encounterId" placeholder="请输入就诊ID" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="患者ID" prop="patientId">
<el-input v-model="enterForm.patientId" placeholder="请输入患者ID" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="患者姓名">
<el-input v-model="enterForm.patientName" placeholder="请输入患者姓名" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="预计住院日">
<el-input-number v-model="enterForm.expectedDays" :min="1" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="预计费用">
<el-input-number v-model="enterForm.expectedCost" :min="0" :precision="2" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<el-button @click="enterDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="submitLoading" @click="submitEnterPathway">确认入径</el-button>
</template>
</el-dialog>
<el-dialog v-model="executionDialogVisible" title="路径执行记录" width="900px" destroy-on-close :close-on-click-modal="false">
<el-table :data="executionList" border style="width: 100%">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="patientName" label="患者" width="100" />
<el-table-column prop="enterDate" label="入径日期" width="120" />
<el-table-column prop="expectedDays" label="预计天数" width="90" align="center" />
<el-table-column prop="actualDays" label="实际天数" width="90" align="center" />
<el-table-column prop="status" label="状态" width="90" align="center">
<template #default="scope">
<el-tag v-if="scope.row.status === 'IN_PATH'" type="primary">在径</el-tag>
<el-tag v-else-if="scope.row.status === 'COMPLETED'" type="success">完成</el-tag>
<el-tag v-else-if="scope.row.status === 'VARIATION'" type="warning">变异</el-tag>
<el-tag v-else-if="scope.row.status === 'EXIT'" type="danger">退出</el-tag>
<el-tag v-else type="info">{{ scope.row.status }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="completeDate" label="完成日期" width="120" />
<el-table-column prop="createTime" label="创建时间" width="170" />
<el-table-column label="操作" width="160" align="center" fixed="right">
<template #default="scope">
<el-button v-if="scope.row.status === 'IN_PATH'" link type="success" @click="handleDischarge(scope.row)">出径</el-button>
<el-button v-if="scope.row.status === 'IN_PATH'" link type="warning" @click="handleVarianceFromExec(scope.row)">变异</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
v-model:current-page="executionQuery.pageNo"
v-model:page-size="executionQuery.pageSize"
:page-sizes="[10, 20, 50]"
layout="total, sizes, prev, pager, next, jumper"
:total="executionTotal"
style="margin-top: 12px; justify-content: flex-end"
@size-change="loadExecutions"
@current-change="loadExecutions"
/>
<template #footer>
<el-button @click="executionDialogVisible = false">关闭</el-button>
</template>
</el-dialog>
<el-dialog v-model="varianceDialogVisible" title="变异记录" width="650px" destroy-on-close :close-on-click-modal="false">
<el-form ref="varianceFormRef" :model="varianceForm" :rules="varianceRules" label-width="110px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="变异日期" prop="varianceDate">
<el-date-picker v-model="varianceForm.varianceDate" type="date" placeholder="选择日期" value-format="YYYY-MM-DD" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="变异类型" prop="varianceType">
<el-select v-model="varianceForm.varianceType" placeholder="请选择" style="width: 100%">
<el-option label="系统变异" value="SYSTEM" />
<el-option label="个人变异" value="PERSONAL" />
<el-option label="疾病变异" value="DISEASE" />
<el-option label="患者变异" value="PATIENT" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="预期项目">
<el-input v-model="varianceForm.expectedItem" placeholder="请输入预期项目" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="实际项目">
<el-input v-model="varianceForm.actualItem" placeholder="请输入实际项目" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="变异原因" prop="varianceReason">
<el-input v-model="varianceForm.varianceReason" type="textarea" :rows="3" placeholder="请输入变异原因" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="调整措施">
<el-input v-model="varianceForm.adjustmentAction" type="textarea" :rows="2" placeholder="请输入调整措施" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="记录人">
<el-input v-model="varianceForm.recordByName" placeholder="请输入记录人姓名" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<el-button @click="varianceDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="submitLoading" @click="submitVariance">保存</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
getPathwayPage, addPathway, admitPathway, recordExecution,
recordVariance, dischargePathway, getPathwayStatistics, getExecutionPage
} from '@/api/clinicalPathway'
const loading = ref(false)
const submitLoading = ref(false)
const stats = ref({})
const pathwayList = ref([])
const pathwayTotal = ref(0)
const pathwayQuery = reactive({ diseaseCode: '', pageNo: 1, pageSize: 10 })
const addDialogVisible = ref(false)
const enterDialogVisible = ref(false)
const executionDialogVisible = ref(false)
const varianceDialogVisible = ref(false)
const addFormRef = ref(null)
const enterFormRef = ref(null)
const varianceFormRef = ref(null)
const addForm = reactive({ diseaseCode: '', diseaseName: '', pathwayName: '', departmentName: '', avgDays: 7, pathwayJson: '' })
const addRules = {
diseaseCode: [{ required: true, message: '请输入疾病编码', trigger: 'blur' }],
diseaseName: [{ required: true, message: '请输入疾病名称', trigger: 'blur' }],
pathwayName: [{ required: true, message: '请输入路径名称', trigger: 'blur' }]
}
const enterForm = reactive({ pathwayId: null, encounterId: '', patientId: '', patientName: '', expectedDays: 7, expectedCost: null })
const enterRules = {
encounterId: [{ required: true, message: '请输入就诊ID', trigger: 'blur' }],
patientId: [{ required: true, message: '请输入患者ID', trigger: 'blur' }]
}
const executionList = ref([])
const executionTotal = ref(0)
const executionQuery = reactive({ pathwayId: null, pageNo: 1, pageSize: 10 })
const varianceForm = reactive({
executionId: null, pathwayId: null, varianceDate: '', varianceType: '',
expectedItem: '', actualItem: '', varianceReason: '', adjustmentAction: '',
recordBy: null, recordByName: ''
})
const varianceRules = {
varianceDate: [{ required: true, message: '请选择变异日期', trigger: 'change' }],
varianceType: [{ required: true, message: '请选择变异类型', trigger: 'change' }],
varianceReason: [{ required: true, message: '请输入变异原因', trigger: 'blur' }]
}
let currentPathway = null
function loadStats() {
getPathwayStatistics().then(res => {
stats.value = res.data || {}
})
}
function loadPathways() {
loading.value = true
getPathwayPage(pathwayQuery).then(res => {
pathwayList.value = res.data?.records || []
pathwayTotal.value = res.data?.total || 0
}).catch(() => {
ElMessage.error('查询失败')
}).finally(() => {
loading.value = false
})
}
function handleAddPathway() {
addForm.diseaseCode = ''
addForm.diseaseName = ''
addForm.pathwayName = ''
addForm.departmentName = ''
addForm.avgDays = 7
addForm.pathwayJson = ''
addDialogVisible.value = true
}
function submitAddPathway() {
addFormRef.value?.validate(valid => {
if (!valid) return
submitLoading.value = true
addPathway({ ...addForm }).then(res => {
if (res.code === 200) {
ElMessage.success('创建成功')
addDialogVisible.value = false
loadPathways()
loadStats()
} else {
ElMessage.error(res.msg || '创建失败')
}
}).catch(() => {
ElMessage.error('创建失败')
}).finally(() => {
submitLoading.value = false
})
})
}
function handleEnterPathway(row) {
currentPathway = row
enterForm.pathwayId = row.id
enterForm.encounterId = ''
enterForm.patientId = ''
enterForm.patientName = ''
enterForm.expectedDays = row.avgDays || 7
enterForm.expectedCost = null
enterDialogVisible.value = true
}
function submitEnterPathway() {
enterFormRef.value?.validate(valid => {
if (!valid) return
submitLoading.value = true
admitPathway({ ...enterForm }).then(res => {
if (res.code === 200) {
ElMessage.success('入径成功')
enterDialogVisible.value = false
loadStats()
} else {
ElMessage.error(res.msg || '入径失败')
}
}).catch(() => {
ElMessage.error('入径失败')
}).finally(() => {
submitLoading.value = false
})
})
}
function handleViewExecution(row) {
currentPathway = row
executionQuery.pathwayId = row.id
executionQuery.pageNo = 1
loadExecutions()
}
function loadExecutions() {
loading.value = true
getExecutionPage(executionQuery).then(res => {
executionList.value = res.data?.records || []
executionTotal.value = res.data?.total || 0
executionDialogVisible.value = true
}).catch(() => {
ElMessage.error('查询执行记录失败')
}).finally(() => {
loading.value = false
})
}
function handleDischarge(row) {
ElMessageBox.confirm('确认出径?出径后将自动计算实际住院天数。', '出径确认', { type: 'success' })
.then(() => {
dischargePathway(row.id).then(res => {
if (res.code === 200) {
ElMessage.success('出径成功')
loadExecutions()
loadStats()
} else {
ElMessage.error(res.msg || '出径失败')
}
}).catch(() => {
ElMessage.error('出径失败')
})
}).catch(() => {})
}
function handleVariance(row) {
currentPathway = row
varianceForm.executionId = null
varianceForm.pathwayId = row.id
varianceForm.varianceDate = ''
varianceForm.varianceType = ''
varianceForm.expectedItem = ''
varianceForm.actualItem = ''
varianceForm.varianceReason = ''
varianceForm.adjustmentAction = ''
varianceForm.recordByName = ''
varianceDialogVisible.value = true
}
function handleVarianceFromExec(row) {
currentPathway = { id: row.pathwayId }
varianceForm.executionId = row.id
varianceForm.pathwayId = row.pathwayId
varianceForm.varianceDate = ''
varianceForm.varianceType = ''
varianceForm.expectedItem = ''
varianceForm.actualItem = ''
varianceForm.varianceReason = ''
varianceForm.adjustmentAction = ''
varianceForm.recordByName = ''
varianceDialogVisible.value = true
}
function submitVariance() {
varianceFormRef.value?.validate(valid => {
if (!valid) return
submitLoading.value = true
recordVariance({ ...varianceForm }).then(res => {
if (res.code === 200) {
ElMessage.success('变异记录已保存')
varianceDialogVisible.value = false
loadStats()
} else {
ElMessage.error(res.msg || '保存失败')
}
}).catch(() => {
ElMessage.error('保存失败')
}).finally(() => {
submitLoading.value = false
})
})
}
onMounted(() => {
loadPathways()
loadStats()
})
</script>
<style scoped>
.clinical-pathway-container {
padding: 12px;
}
.stats-row {
margin-bottom: 16px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.query-form {
margin-bottom: 0;
}
</style>