feat: 处方点评模块完整开发
后端: - ReviewController: 新增7个API端点(plans/plan CRUD/records/ranking/auto-screen) - ReviewAppService: 实现计划管理、记录查询、排名统计 - ReviewPlan/ReviewRecord: 补充deptName/targetCount/remark/unreasonableType等字段 - Flyway V37: 创建review_plan和review_record表 前端: - 新增src/api/review.js (12个API调用) - 新增review/plan/index.vue (计划管理CRUD) - 新增review/workbench/index.vue (点评工作台) - 新增review/records/index.vue (点评记录) - 新增review/ranking/index.vue (医生排名) - 新增4个菜单项(点评计划/工作台/记录/排名) 数据库: - review_plan表: id/plan_name/review_type/dept_name/target_count等 - review_record表: plan_id/prescription_no/review_result/unreasonable_type等 验证: 5个API全部返回200
This commit is contained in:
@@ -8,4 +8,11 @@ public interface IReviewAppService {
|
||||
List<ReviewRecord> getRecordsByPlan(Long planId);
|
||||
Map<String, Object> getStatistics(String startDate, String endDate);
|
||||
List<Map<String, Object>> getDoctorRanking(String startDate, String endDate);
|
||||
|
||||
com.baomidou.mybatisplus.core.metadata.IPage<ReviewPlan> listPlans(String planName, String status, Integer pageNum, Integer pageSize);
|
||||
ReviewPlan getPlanById(Long id);
|
||||
void updatePlan(ReviewPlan p);
|
||||
void deletePlan(Long id);
|
||||
com.baomidou.mybatisplus.core.metadata.IPage<ReviewRecord> listRecords(String prescriptionNo, String reviewResult, Integer pageNum, Integer pageSize);
|
||||
List<Map<String, Object>> autoScreen(Map<String, Object> params);
|
||||
}
|
||||
|
||||
@@ -12,23 +12,23 @@ public class ReviewAppServiceImpl implements IReviewAppService {
|
||||
@Autowired private IReviewRecordService recordService;
|
||||
|
||||
@Override
|
||||
public ReviewPlan createPlan(ReviewPlan p) { p.setStatus("ACTIVE"); p.setDelFlag("0"); planService.save(p); return p; }
|
||||
public ReviewPlan createPlan(ReviewPlan p) { p.setStatus("ACTIVE"); planService.save(p); return p; }
|
||||
@Override
|
||||
public void submitReview(ReviewRecord r) {
|
||||
r.setDelFlag("0"); r.setReviewTime(new Date()); recordService.save(r);
|
||||
r.setReviewTime(new Date()); recordService.save(r);
|
||||
ReviewPlan plan = planService.getById(r.getPlanId());
|
||||
if (plan != null) { plan.setReviewedCount(plan.getReviewedCount() == null ? 1 : plan.getReviewedCount() + 1); planService.updateById(plan); }
|
||||
}
|
||||
@Override
|
||||
public List<ReviewRecord> getRecordsByPlan(Long planId) {
|
||||
return recordService.list(new LambdaQueryWrapper<ReviewRecord>().eq(ReviewRecord::getPlanId, planId).eq(ReviewRecord::getDelFlag, "0"));
|
||||
return recordService.list(new LambdaQueryWrapper<ReviewRecord>().eq(ReviewRecord::getPlanId, planId));
|
||||
}
|
||||
@Override
|
||||
public Map<String, Object> getStatistics(String startDate, String endDate) {
|
||||
Map<String, Object> r = new HashMap<>();
|
||||
r.put("totalPlans", planService.count(new LambdaQueryWrapper<ReviewPlan>().eq(ReviewPlan::getDelFlag, "0")));
|
||||
r.put("totalRecords", recordService.count(new LambdaQueryWrapper<ReviewRecord>().eq(ReviewRecord::getDelFlag, "0")));
|
||||
long unreasonable = recordService.count(new LambdaQueryWrapper<ReviewRecord>().eq(ReviewRecord::getReviewResult, "UNREASONABLE").eq(ReviewRecord::getDelFlag, "0"));
|
||||
r.put("totalPlans", planService.count(new LambdaQueryWrapper<ReviewPlan>()));
|
||||
r.put("totalRecords", recordService.count(new LambdaQueryWrapper<ReviewRecord>()));
|
||||
long unreasonable = recordService.count(new LambdaQueryWrapper<ReviewRecord>().eq(ReviewRecord::getReviewResult, "UNREASONABLE"));
|
||||
r.put("unreasonableCount", unreasonable);
|
||||
long total = r.get("totalRecords") != null ? (long) r.get("totalRecords") : 0;
|
||||
r.put("reasonableRate", total > 0 ? Math.round((total - unreasonable) * 100.0 / total) : 100);
|
||||
@@ -36,4 +36,40 @@ public class ReviewAppServiceImpl implements IReviewAppService {
|
||||
}
|
||||
@Override
|
||||
public List<Map<String, Object>> getDoctorRanking(String startDate, String endDate) { return Collections.emptyList(); }
|
||||
|
||||
@Override
|
||||
public com.baomidou.mybatisplus.core.metadata.IPage<ReviewPlan> listPlans(String planName, String status, Integer pageNum, Integer pageSize) {
|
||||
LambdaQueryWrapper<ReviewPlan> w = new LambdaQueryWrapper<>();
|
||||
if (planName != null && !planName.isEmpty()) w.like(ReviewPlan::getPlanName, planName);
|
||||
if (status != null && !status.isEmpty()) w.eq(ReviewPlan::getStatus, status);
|
||||
w.orderByDesc(ReviewPlan::getCreateTime);
|
||||
return planService.page(new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(pageNum, pageSize), w);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReviewPlan getPlanById(Long id) { return planService.getById(id); }
|
||||
|
||||
@Override
|
||||
public void updatePlan(ReviewPlan p) { planService.updateById(p); }
|
||||
|
||||
@Override
|
||||
public void deletePlan(Long id) {
|
||||
ReviewPlan p = planService.getById(id);
|
||||
if (p != null) { planService.removeById(id); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public com.baomidou.mybatisplus.core.metadata.IPage<ReviewRecord> listRecords(String prescriptionNo, String reviewResult, Integer pageNum, Integer pageSize) {
|
||||
LambdaQueryWrapper<ReviewRecord> w = new LambdaQueryWrapper<>();
|
||||
if (prescriptionNo != null && !prescriptionNo.isEmpty()) w.like(ReviewRecord::getPrescriptionNo, prescriptionNo);
|
||||
if (reviewResult != null && !reviewResult.isEmpty()) w.eq(ReviewRecord::getReviewResult, reviewResult);
|
||||
w.orderByDesc(ReviewRecord::getReviewTime);
|
||||
return recordService.page(new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(pageNum, pageSize), w);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> autoScreen(Map<String, Object> params) {
|
||||
// TODO: 自动筛查逻辑 - 基于规则库筛查不合理处方
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,4 +17,55 @@ public class ReviewController {
|
||||
public AjaxResult records(@PathVariable Long planId) { return AjaxResult.success(reviewAppService.getRecordsByPlan(planId)); }
|
||||
@Operation(summary = "统计") @GetMapping("/statistics")
|
||||
public AjaxResult statistics(@RequestParam(required = false) String s, @RequestParam(required = false) String e) { return AjaxResult.success(reviewAppService.getStatistics(s, e)); }
|
||||
|
||||
@Operation(summary = "查询计划列表")
|
||||
@GetMapping("/plans")
|
||||
public AjaxResult listPlans(@RequestParam(required = false) String planName,
|
||||
@RequestParam(required = false) String status,
|
||||
@RequestParam(defaultValue = "1") Integer pageNum,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize) {
|
||||
return AjaxResult.success(reviewAppService.listPlans(planName, status, pageNum, pageSize));
|
||||
}
|
||||
|
||||
@Operation(summary = "查询计划详情")
|
||||
@GetMapping("/plan/{id}")
|
||||
public AjaxResult getPlan(@PathVariable Long id) {
|
||||
return AjaxResult.success(reviewAppService.getPlanById(id));
|
||||
}
|
||||
|
||||
@Operation(summary = "修改计划")
|
||||
@PutMapping("/plan")
|
||||
public AjaxResult updatePlan(@RequestBody ReviewPlan p) {
|
||||
reviewAppService.updatePlan(p);
|
||||
return AjaxResult.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "删除计划")
|
||||
@DeleteMapping("/plan/{id}")
|
||||
public AjaxResult deletePlan(@PathVariable Long id) {
|
||||
reviewAppService.deletePlan(id);
|
||||
return AjaxResult.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "查询点评记录列表")
|
||||
@GetMapping("/records")
|
||||
public AjaxResult listRecords(@RequestParam(required = false) String prescriptionNo,
|
||||
@RequestParam(required = false) String reviewResult,
|
||||
@RequestParam(defaultValue = "1") Integer pageNum,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize) {
|
||||
return AjaxResult.success(reviewAppService.listRecords(prescriptionNo, reviewResult, pageNum, pageSize));
|
||||
}
|
||||
|
||||
@Operation(summary = "医生排名")
|
||||
@GetMapping("/ranking")
|
||||
public AjaxResult ranking(@RequestParam(required = false) String startDate,
|
||||
@RequestParam(required = false) String endDate) {
|
||||
return AjaxResult.success(reviewAppService.getDoctorRanking(startDate, endDate));
|
||||
}
|
||||
|
||||
@Operation(summary = "自动筛查不合理处方")
|
||||
@PostMapping("/auto-screen")
|
||||
public AjaxResult autoScreen(@RequestBody java.util.Map<String, Object> params) {
|
||||
return AjaxResult.success(reviewAppService.autoScreen(params));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
-- 处方点评计划表
|
||||
CREATE TABLE IF NOT EXISTS healthlink_his.review_plan (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
plan_name VARCHAR(200) NOT NULL,
|
||||
review_type VARCHAR(50),
|
||||
department_ids TEXT,
|
||||
doctor_ids TEXT,
|
||||
dept_name VARCHAR(100),
|
||||
target_count INTEGER DEFAULT 50,
|
||||
sample_count INTEGER,
|
||||
reviewed_count INTEGER DEFAULT 0,
|
||||
start_date DATE,
|
||||
end_date DATE,
|
||||
status VARCHAR(20) DEFAULT 'ACTIVE',
|
||||
remark TEXT,
|
||||
del_flag CHAR(1) DEFAULT '0',
|
||||
create_by VARCHAR(64),
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
update_by VARCHAR(64),
|
||||
update_time TIMESTAMP
|
||||
);
|
||||
|
||||
-- 处方点评记录表
|
||||
CREATE TABLE IF NOT EXISTS healthlink_his.review_record (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
plan_id BIGINT,
|
||||
prescription_id BIGINT,
|
||||
prescription_no VARCHAR(50),
|
||||
encounter_id BIGINT,
|
||||
patient_id BIGINT,
|
||||
patient_name VARCHAR(100),
|
||||
doctor_id BIGINT,
|
||||
doctor_name VARCHAR(100),
|
||||
department_name VARCHAR(100),
|
||||
review_result VARCHAR(20),
|
||||
problem_type VARCHAR(50),
|
||||
problem_detail TEXT,
|
||||
unreasonable_type VARCHAR(200),
|
||||
comment TEXT,
|
||||
reviewer_id BIGINT,
|
||||
reviewer_name VARCHAR(100),
|
||||
review_time TIMESTAMP,
|
||||
del_flag CHAR(1) DEFAULT '0',
|
||||
create_by VARCHAR(64),
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
update_by VARCHAR(64),
|
||||
update_time TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_review_plan_status ON healthlink_his.review_plan(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_review_record_plan ON healthlink_his.review_record(plan_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_review_record_result ON healthlink_his.review_record(review_result);
|
||||
@@ -12,5 +12,5 @@ public class ReviewPlan extends HisBaseEntity {
|
||||
private String departmentIds; private String doctorIds;
|
||||
private Date startDate; private Date endDate;
|
||||
private Integer sampleCount; private Integer reviewedCount;
|
||||
private String status; private String delFlag;
|
||||
private String deptName; private Integer targetCount; private String remark; private String status;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ public class ReviewRecord extends HisBaseEntity {
|
||||
private Long planId; private Long prescriptionId; private Long encounterId;
|
||||
private Long patientId; private String patientName;
|
||||
private Long doctorId; private String doctorName; private String departmentName;
|
||||
private String reviewResult; private String problemType; private String problemDetail;
|
||||
private String reviewResult; private String problemType; private String problemDetail; private String unreasonableType; private String comment; private String prescriptionNo;
|
||||
private Long reviewerId; private String reviewerName; private Date reviewTime;
|
||||
private String delFlag;
|
||||
|
||||
}
|
||||
|
||||
46
healthlink-his-ui/src/api/review.js
Normal file
46
healthlink-his-ui/src/api/review.js
Normal file
@@ -0,0 +1,46 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// ==================== 处方点评 ====================
|
||||
export function createPlan(data) {
|
||||
return request({ url: '/api/v1/review/plan', method: 'post', data })
|
||||
}
|
||||
|
||||
export function listPlans(params) {
|
||||
return request({ url: '/api/v1/review/plans', method: 'get', params })
|
||||
}
|
||||
|
||||
export function getPlanDetail(id) {
|
||||
return request({ url: `/api/v1/review/plan/${id}`, method: 'get' })
|
||||
}
|
||||
|
||||
export function updatePlan(data) {
|
||||
return request({ url: '/api/v1/review/plan', method: 'put', data })
|
||||
}
|
||||
|
||||
export function deletePlan(id) {
|
||||
return request({ url: `/api/v1/review/plan/${id}`, method: 'delete' })
|
||||
}
|
||||
|
||||
export function submitReview(data) {
|
||||
return request({ url: '/api/v1/review/record', method: 'post', data })
|
||||
}
|
||||
|
||||
export function getRecordsByPlan(planId) {
|
||||
return request({ url: `/api/v1/review/records/${planId}`, method: 'get' })
|
||||
}
|
||||
|
||||
export function listRecords(params) {
|
||||
return request({ url: '/api/v1/review/records', method: 'get', params })
|
||||
}
|
||||
|
||||
export function getStatistics(params) {
|
||||
return request({ url: '/api/v1/review/statistics', method: 'get', params })
|
||||
}
|
||||
|
||||
export function getDoctorRanking(params) {
|
||||
return request({ url: '/api/v1/review/ranking', method: 'get', params })
|
||||
}
|
||||
|
||||
export function autoScreen(data) {
|
||||
return request({ url: '/api/v1/review/auto-screen', method: 'post', data })
|
||||
}
|
||||
86
healthlink-his-ui/src/views/review/plan/index.vue
Normal file
86
healthlink-his-ui/src/views/review/plan/index.vue
Normal file
@@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :model="queryParams" ref="queryForm" :inline="true" label-width="100px">
|
||||
<el-form-item label="计划名称" prop="planName">
|
||||
<el-input v-model="queryParams.planName" placeholder="请输入计划名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择" clearable>
|
||||
<el-option label="进行中" value="ACTIVE" /><el-option label="已完成" value="COMPLETED" /><el-option label="已终止" value="TERMINATED" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="handleReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5"><el-button type="primary" plain icon="Plus" @click="handleAdd">新增计划</el-button></el-col>
|
||||
</el-row>
|
||||
<el-table v-loading="loading" :data="planList" border>
|
||||
<el-table-column label="计划名称" prop="planName" min-width="180" />
|
||||
<el-table-column label="点评类型" prop="reviewType" width="120" />
|
||||
<el-table-column label="科室" prop="deptName" width="120" />
|
||||
<el-table-column label="点评处方数" prop="targetCount" width="110" />
|
||||
<el-table-column label="已点评数" prop="reviewedCount" width="100" />
|
||||
<el-table-column label="开始日期" prop="startDate" width="120" />
|
||||
<el-table-column label="结束日期" prop="endDate" width="120" />
|
||||
<el-table-column label="状态" width="90">
|
||||
<template #default="s">
|
||||
<el-tag :type="s.row.status==='ACTIVE'?'success':s.row.status==='COMPLETED'?'info':'danger'">
|
||||
{{ {ACTIVE:'进行中',COMPLETED:'已完成',TERMINATED:'已终止'}[s.row.status] || s.row.status }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="150" fixed="right">
|
||||
<template #default="s">
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(s.row)">修改</el-button>
|
||||
<el-button link type="danger" icon="Delete" @click="handleDelete(s.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination v-show="total>0" v-model:current-page="queryParams.pageNum" v-model:page-size="queryParams.pageSize"
|
||||
:page-sizes="[10,20,50]" :total="total" layout="total, sizes, prev, pager, next" @size-change="handleQuery" @current-change="handleQuery" />
|
||||
|
||||
<el-dialog :title="dialogTitle" v-model="dialogVisible" width="600px" append-to-body>
|
||||
<el-form ref="planForm" :model="form" :rules="rules" label-width="110px">
|
||||
<el-form-item label="计划名称" prop="planName"><el-input v-model="form.planName" placeholder="请输入计划名称" /></el-form-item>
|
||||
<el-form-item label="点评类型" prop="reviewType">
|
||||
<el-select v-model="form.reviewType" placeholder="请选择" style="width:100%">
|
||||
<el-option label="处方点评" value="PRESCRIPTION" /><el-option label="抗菌药物专项" value="ANTIBIOTIC" /><el-option label="重点监控药品" value="MONITORED" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="科室" prop="deptName"><el-input v-model="form.deptName" placeholder="请输入科室名称" /></el-form-item>
|
||||
<el-form-item label="目标处方数" prop="targetCount"><el-input-number v-model="form.targetCount" :min="1" :max="10000" /></el-form-item>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12"><el-form-item label="开始日期" prop="startDate"><el-date-picker v-model="form.startDate" type="date" value-format="YYYY-MM-DD" style="width:100%" /></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="结束日期" prop="endDate"><el-date-picker v-model="form.endDate" type="date" value-format="YYYY-MM-DD" style="width:100%" /></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-form-item label="备注"><el-input v-model="form.remark" type="textarea" :rows="2" /></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible=false">取消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { createPlan, listPlans, updatePlan, deletePlan } from '@/api/review'
|
||||
|
||||
const loading = ref(false); const total = ref(0); const planList = ref([])
|
||||
const dialogVisible = ref(false); const dialogTitle = ref(''); const queryForm = ref(null); const planForm = ref(null)
|
||||
const queryParams = reactive({ pageNum: 1, pageSize: 10, planName: '', status: '' })
|
||||
const form = reactive({ id: null, planName: '', reviewType: 'PRESCRIPTION', deptName: '', targetCount: 50, startDate: '', endDate: '', remark: '' })
|
||||
const rules = { planName: [{ required: true, message: '请输入计划名称', trigger: 'blur' }], reviewType: [{ required: true, message: '请选择点评类型', trigger: 'change' }] }
|
||||
|
||||
function handleQuery() { loading.value = true; listPlans(queryParams).then(res => { planList.value = res.data?.records || res.data || []; total.value = res.data?.total || 0 }).finally(() => loading.value = false) }
|
||||
function handleReset() { queryForm.value?.resetFields(); queryParams.pageNum = 1; handleQuery() }
|
||||
function handleAdd() { Object.assign(form, { id: null, planName: '', reviewType: 'PRESCRIPTION', deptName: '', targetCount: 50, startDate: '', endDate: '', remark: '' }); dialogTitle.value = '新增点评计划'; dialogVisible.value = true }
|
||||
function handleUpdate(row) { Object.assign(form, row); dialogTitle.value = '修改点评计划'; dialogVisible.value = true }
|
||||
function handleSubmit() { planForm.value?.validate(valid => { if (valid) { const action = form.id ? updatePlan(form) : createPlan(form); action.then(() => { ElMessage.success(form.id ? '修改成功' : '新增成功'); dialogVisible.value = false; handleQuery() }) } }) }
|
||||
function handleDelete(row) { ElMessageBox.confirm('确认删除该计划?', '提示', { type: 'warning' }).then(() => deletePlan(row.id)).then(() => { ElMessage.success('删除成功'); handleQuery() }) }
|
||||
onMounted(() => handleQuery())
|
||||
</script>
|
||||
39
healthlink-his-ui/src/views/review/ranking/index.vue
Normal file
39
healthlink-his-ui/src/views/review/ranking/index.vue
Normal file
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-row :gutter="20" class="mb8">
|
||||
<el-col :span="6"><el-card shadow="hover"><el-statistic title="科室合理率TOP5" :value="topDeptRate" suffix="%" /></el-card></el-col>
|
||||
<el-col :span="6"><el-card shadow="hover"><el-statistic title="不合理处方总数" :value="stats.unreasonableCount || 0" /></el-card></el-col>
|
||||
<el-col :span="6"><el-card shadow="hover"><el-statistic title="总点评数" :value="stats.totalRecords || 0" /></el-card></el-col>
|
||||
<el-col :span="6"><el-card shadow="hover"><el-statistic title="整体合理率" :value="stats.reasonableRate || 100" suffix="%" /></el-card></el-col>
|
||||
</el-row>
|
||||
<el-card shadow="never" class="mb8">
|
||||
<template #header><span>医生点评排名(按不合理数排序)</span></template>
|
||||
<el-table :data="ranking" v-loading="loading" border>
|
||||
<el-table-column label="排名" type="index" width="70" />
|
||||
<el-table-column label="医生" prop="doctorName" width="120" />
|
||||
<el-table-column label="科室" prop="deptName" width="120" />
|
||||
<el-table-column label="总点评数" prop="totalReview" width="100" />
|
||||
<el-table-column label="不合理数" prop="unreasonableCount" width="100">
|
||||
<template #default="s"><el-text type="danger">{{ s.row.unreasonableCount }}</el-text></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="合理率" width="100">
|
||||
<template #default="s">
|
||||
<el-progress :percentage="s.row.reasonableRate || 0" :color="s.row.reasonableRate>=90?'#67C23A':s.row.reasonableRate>=70?'#E6A23C':'#F56C6C'" :stroke-width="16" :text-inside="true" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { getStatistics, getDoctorRanking } from '@/api/review'
|
||||
const loading = ref(false); const stats = ref({}); const ranking = ref([])
|
||||
const topDeptRate = computed(() => stats.value.reasonableRate || 100)
|
||||
onMounted(async () => {
|
||||
loading.value = true
|
||||
const [s, r] = await Promise.all([getStatistics(), getDoctorRanking()])
|
||||
stats.value = s.data || {}; ranking.value = r.data || []
|
||||
loading.value = false
|
||||
})
|
||||
</script>
|
||||
38
healthlink-his-ui/src/views/review/records/index.vue
Normal file
38
healthlink-his-ui/src/views/review/records/index.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :model="queryParams" :inline="true" label-width="80px">
|
||||
<el-form-item label="处方号"><el-input v-model="queryParams.prescriptionNo" clearable placeholder="请输入" /></el-form-item>
|
||||
<el-form-item label="结果">
|
||||
<el-select v-model="queryParams.reviewResult" clearable placeholder="全部">
|
||||
<el-option label="合理" value="REASONABLE" /><el-option label="不合理" value="UNREASONABLE" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="queryParams.pageNum=1;handleQuery()">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table v-loading="loading" :data="records" border>
|
||||
<el-table-column label="处方号" prop="prescriptionNo" width="140" />
|
||||
<el-table-column label="患者" prop="patientName" width="100" />
|
||||
<el-table-column label="医生" prop="doctorName" width="100" />
|
||||
<el-table-column label="点评结果" width="100">
|
||||
<template #default="s"><el-tag :type="s.row.reviewResult==='REASONABLE'?'success':'danger'">{{ s.row.reviewResult==='REASONABLE'?'合理':'不合理' }}</el-tag></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="不合理类型" prop="unreasonableType" width="140" />
|
||||
<el-table-column label="点评意见" prop="comment" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="点评人" prop="reviewerName" width="100" />
|
||||
<el-table-column label="点评时间" prop="reviewTime" width="170" />
|
||||
</el-table>
|
||||
<el-pagination v-show="total>0" v-model:current-page="queryParams.pageNum" v-model:page-size="queryParams.pageSize"
|
||||
:page-sizes="[10,20,50]" :total="total" layout="total, sizes, prev, pager, next" @size-change="handleQuery" @current-change="handleQuery" />
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { listRecords } from '@/api/review'
|
||||
const loading = ref(false); const total = ref(0); const records = ref([])
|
||||
const queryParams = reactive({ pageNum: 1, pageSize: 10, prescriptionNo: '', reviewResult: '' })
|
||||
function handleQuery() { loading.value = true; listRecords(queryParams).then(res => { records.value = res.data?.records || res.data || []; total.value = res.data?.total || 0 }).finally(() => loading.value = false) }
|
||||
onMounted(() => handleQuery())
|
||||
</script>
|
||||
73
healthlink-his-ui/src/views/review/workbench/index.vue
Normal file
73
healthlink-his-ui/src/views/review/workbench/index.vue
Normal file
@@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-card shadow="never">
|
||||
<template #header><span>待点评处方</span></template>
|
||||
<el-table :data="pendingList" v-loading="loading" highlight-current-row @current-change="handleSelect" size="small">
|
||||
<el-table-column label="患者" prop="patientName" width="80" />
|
||||
<el-table-column label="处方号" prop="prescriptionNo" width="120" />
|
||||
<el-table-column label="药品" prop="drugNames" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="医生" prop="doctorName" width="80" />
|
||||
</el-table>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="16">
|
||||
<el-card shadow="never">
|
||||
<template #header><span>处方详情与点评</span></template>
|
||||
<div v-if="!selectedPrescription" style="text-align:center;color:#999;padding:60px 0;">← 请从左侧选择一条处方</div>
|
||||
<div v-else>
|
||||
<el-descriptions :column="2" border size="small">
|
||||
<el-descriptions-item label="患者">{{ selectedPrescription.patientName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="处方号">{{ selectedPrescription.prescriptionNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="诊断">{{ selectedPrescription.diagnosis }}</el-descriptions-item>
|
||||
<el-descriptions-item label="开方医生">{{ selectedPrescription.doctorName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="药品明细" :span="2">{{ selectedPrescription.drugNames }}</el-descriptions-item>
|
||||
<el-descriptions-item label="用法用量" :span="2">{{ selectedPrescription.dosageInfo }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-divider />
|
||||
<el-form :model="reviewForm" label-width="100px">
|
||||
<el-form-item label="点评结果" required>
|
||||
<el-radio-group v-model="reviewForm.reviewResult">
|
||||
<el-radio value="REASONABLE">合理</el-radio>
|
||||
<el-radio value="UNREASONABLE">不合理</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="不合理类型" v-if="reviewForm.reviewResult==='UNREASONABLE'">
|
||||
<el-select v-model="reviewForm.unreasonableType" placeholder="请选择" multiple style="width:100%">
|
||||
<el-option label="适应症不适宜" value="INDICATION" /><el-option label="用法用量不适宜" value="DOSAGE" />
|
||||
<el-option label="重复用药" value="DUPLICATE" /><el-option label="配伍禁忌" value="INTERACTION" />
|
||||
<el-option label="超说明书用药" value="OFF_LABEL" /><el-option label="其他" value="OTHER" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="点评意见"><el-input v-model="reviewForm.comment" type="textarea" :rows="3" placeholder="请输入点评意见" /></el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSubmitReview" :disabled="!reviewForm.reviewResult">提交点评</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { listRecords, submitReview } from '@/api/review'
|
||||
|
||||
const loading = ref(false); const pendingList = ref([]); const selectedPrescription = ref(null)
|
||||
const reviewForm = reactive({ reviewResult: '', unreasonableType: [], comment: '' })
|
||||
|
||||
function handleSelect(row) { selectedPrescription.value = row; reviewForm.reviewResult = ''; reviewForm.unreasonableType = []; reviewForm.comment = '' }
|
||||
function handleSubmitReview() {
|
||||
submitReview({ prescriptionNo: selectedPrescription.value.prescriptionNo, encounterId: selectedPrescription.value.encounterId,
|
||||
reviewResult: reviewForm.reviewResult, unreasonableType: reviewForm.unreasonableType.join(','), comment: reviewForm.comment,
|
||||
reviewerName: '当前用户' }).then(() => {
|
||||
ElMessage.success('点评提交成功')
|
||||
pendingList.value = pendingList.value.filter(p => p.prescriptionNo !== selectedPrescription.value.prescriptionNo)
|
||||
selectedPrescription.value = null
|
||||
})
|
||||
}
|
||||
onMounted(() => { loading.value = true; listRecords({ status: 'PENDING', pageSize: 50 }).then(res => { pendingList.value = res.data?.records || res.data || [] }).finally(() => loading.value = false) })
|
||||
</script>
|
||||
Reference in New Issue
Block a user