2025-11-26 18:09:02 +08:00

767 lines
23 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="app-container">
<el-row :gutter="10">
<el-col :span="13">
<div class="box-shadow">
<el-form :model="queryParams" ref="queryRef" :inline="true" :size="aotuSize" :rules="rules"
class="compact-form">
<el-row :gutter="5">
<el-col :span="6">
<el-form-item label="" prop="ybh">
<div class="next_box">
<div class="jtIcon" @click="handlePrev">
<el-icon>
<ArrowUpBold />
</el-icon>
</div>
<el-input v-model="queryParams.ybh" placeholder="样本编号" @keyup.enter="handleQuery"
style="width:100%" />
<div class="jtIcon" @click="handleNext">
<el-icon>
<ArrowDownBold />
</el-icon>
</div>
</div>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="日期:" prop="jyrq">
<el-date-picker v-model="queryParams.jyrq" type="date" label-width="1.25rem" placeholder="检验日期"
value-format="YYYY-MM-DD" :clearable="false" @change="fetchlabPatList"
style="width:100%"></el-date-picker>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="部门:" prop="instrGroup">
<SelectTable v-model:data="queryParams.instrGroup" :fields="instrGroupFields"
:tableData="instrGroupOptions" label="zdmc" value="zddh" objKey="zddh" :border="true" width="9rem"
placeholder="请选择部门" @getDataValue="handleinstrGroupChange" :clearable="false" :size="aotuSize" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="仪器:" prop="yq">
<SelectTable v-model:data="queryParams.yq" :fields="yqFields" :tableData="instrOptions" label="yqmc"
value="yq" objKey="yq" :border="true" width="9rem" placeholder="请选择仪器" :clearable="false"
:size="aotuSize" @getDataValue="yqHandleChange" />
</el-form-item>
</el-col>
</el-row>
<!-- <el-form-item>
<el-icon color="#3c80d9" size="20" style="margin-right: 10px;" class="cp">
<Search @click="fetchlabPatList" />
</el-icon>
<el-icon color="#3c80d9" size="20" class="cp" @click="selectJob(labPat)">
<Refresh />
</el-icon>
</el-form-item> -->
</el-form>
<el-row :gutter="5">
<el-col :span="8">
<LabPat ref="labPatRef" v-model:labPat="labPat" :labPatKey="queryParams" :labSysList="labInputcolList"
:userList="userList" @changeStatus="changeStatus" @batchShow="handleBatchScan" />
</el-col>
<el-col :span="16">
<LabResult ref="labResultRef" :labPat="labPat" :tableData="labResuts" :tableKey="queryParams"
@update:tableKey="queryParams = $event" :userList="userList" :resultSysList="resultConfig"
@fetchLabResults="resultsHandle" :dictData="dictData" @changeStatus="changeStatus"
@previewHandle="previewHandle" />
</el-col>
</el-row>
</div>
</el-col>
<el-col :span="11">
<div>
<tabView :tabList="tabList" v-model:activeTab="activeTab" />
<div class="box-shadow right_box">
<template v-if="activeTab == 'LabPatList'">
<div class="flex-between">
<div>
<el-button type="primary" :size="aotuSize" icon="RefreshRight" @click="fetchlabPatList">刷新</el-button>
<el-button type="danger" :size="aotuSize" @click="delSampleHd">删除</el-button>
<el-button class="btn_green" :size="aotuSize" icon="top" @click="handlePrev">上一个</el-button>
<el-button class="btn_green mr5" :size="aotuSize" icon="bottom" @click="handleNext">下一个</el-button>
<PatientQueryForm :initial-params="initialParams" @query="handleQueryResult" @reset="handleReset"
width="9rem" :size="aotuSize" placeholder="查询条件" :dict-data="dictData" :clearable="true" />
<el-input v-model="searchText" clearable :size="aotuSize" @keyup.enter="searchQuery"
@clear="clearSearch" style="width: 6rem" />
</div>
<div>
<el-checkbox v-model="lbFlag" :size="aotuSize"> 列表翻页 </el-checkbox>
</div>
</div>
<LabPatList ref="labPatListRef" :labPatList="labPatList" :loading="loading" @select="selectJob"
:tableKey="queryParams" :dictData="dictData" @search="searchJobs" />
<div class="button-group">
<div class="text1">样本总数:<span>{{ ybs }}</span></div>
<div class="text2">未审:<span>{{ ws }}</span></div>
<div class="text3">危:<span>{{ wjs }}</span></div>
</div>
</template>
<component :is="activeTabName" :queryParams="queryParams" :dictData="dictData" :instrOptions="instrOptions"
@fetchResults="fetchLabResults" :tablekey="queryParams" />
</div>
</div>
</el-col>
</el-row>
<!-- 批量扫码弹窗 -->
<BatchCode ref="batchRef" v-model:labPat="labPat" :labPatKey="queryParams" :dictData="dictData"
:totalCount="labPatList.length" @goNext="handleNext" @goPrev="handlePrev" @goLast="handleLast"
@queryYbh="ybhChangeTb" @changeStatus="changeStatus" />
<el-dialog v-model="previewShow" title="预览" width="90vw" :close-on-click-modal="false">
<iFrame v-if="pdfUrl" :src="pdfUrl" />
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, nextTick, onMounted, toRaw } from 'vue';
import LabPat from './components/labpat.vue';
import SelectTable from '@/components/SelectTable/index.vue';
import tabView from '@/components/tabViews/index.vue';
// @ts-ignore
import iFrame from "@/components/iFrame/index.vue";
// @ts-ignore
import LabResult from './components/labresult.vue';
import LabPatList from './components/labpatlist.vue';
// @ts-ignore
import { comDict } from '@/utils/dict'
import { queryLabPatList, queryLabResults, loadDefault, queryLabPat, instrdconfig, delSample, getGroupInstrdList } from "@/api/liswork/work/LisWork";
import { getComDicts, queryComDictListService } from "@/api/liswork/dict/ComDict";
import { ElMessage, ElMessageBox } from "element-plus";
import History from '../components/history/index.vue'
import Others from '../components/others/index.vue'
import Reexamination from '../components/reexamination/index.vue'
import Charge from '../components/charge/index.vue'
import Combination from '../components/combination/index.vue'
import Clinical from '../components/clinical/index.vue'
import Sample from '../components/sample/index.vue'
import ResultGraph from '../components/resultGraph/index.vue'
import { getNextNumber, getPreviousNumber } from "@/utils/getNextNumber";
import { useCommonStore } from '@/store/modules/commonStore';
import { classCom } from '@/utils/classCom';
import PatientQueryForm from '@/components/selectSearch/index.vue'
import BatchCode from './components/batchCode.vue';
// @ts-ignore
import { listUser } from '@/api/system/user.js'
const aotuSize = classCom.useAutoSize();
import { initWebSocket, sendWebSocketMessage, closeWebSocket, getWebSocketState } from '@/utils/webSocket';
const previewShow = ref(false);
const lbFlag = ref(false);
const queryParams = ref({
jyrq: '2025-08-20',
yq: '46',
ybh: '1',
instrGroup: '02',
problemId: 0,
days: 0,
yhdh: ''
})
const ybs = ref(0)
const ws = ref(0)
const wjs = ref(0)
const activeTab = ref('LabPatList')
const tabList = ref([
{ label: '标本列表', value: 'LabPatList' },
{ label: '结果图形', value: 'ResultGraph' },
{ label: '历史对照', value: 'History' },
{ label: '其他结果', value: 'Others' },
{ label: '结果复查', value: 'Reexamination' },
{ label: '收费信息', value: 'Charge' },
{ label: '组合项目', value: 'Combination' },
{ label: '临床意义', value: 'Clinical' },
{ label: '样本流转', value: 'Sample' },
])
const activeTabMap = {
ResultGraph,
History,
Others,
Reexamination,
Charge,
Combination,
Clinical,
Sample
}
const activeTabName = computed(() => activeTabMap[activeTab.value as keyof typeof activeTabMap] || 'LabPatList');
const labPat = ref<any>({})
const labPatList = ref<Array<{ ybh: string; jgbz: string }>>([]);
const originalLabPatList = ref([]) //原始数据
const loading = ref(false)
const searchText = ref('')
const rules = ref([])
const instrGroupOptions = ref<any[]>([])
const instrOptions = ref<{ yq: string; yqmc: string }[]>([])
const pdfUrl = ref('')
// 初始查询参数(可选)
const initialParams = {}
// 定义查询参数类型
interface QueryParams {
brly?: string | number
jgbz?: string | number
dybz?: string | number
alarmflag?: string | number
jzbz?: string | number
finish?: string | number
autojgbz?: string | number
}
// 模糊查询
const searchQuery = () => {
let filterMhList = [...originalLabPatList.value];
if (searchText.value) {
const searchStr = searchText.value.trim().toLowerCase();
filterMhList = filterMhList.filter((item: any) => {
// 处理 zjf大写转为小写
const matchZjf = item.zjf && typeof item.zjf === 'string'
? item.zjf.toLowerCase().includes(searchStr)
: false; const matchBrxm = item.brxm && typeof item.brxm === 'string' && item.brxm.includes(searchStr);
const matchSqh = item.sqh && typeof item.sqh === 'string' && item.sqh.includes(searchStr);
return matchZjf || matchBrxm || matchSqh;
});
}
labPatList.value = filterMhList;
getTotals();
if (filterMhList.length > 0) {
highlightRowIndex.value = 0;
const firstRow = filterMhList[0];
labPatListRef.value.setCurrentRow(firstRow);
selectJob(firstRow);
} else {
labPat.value = {};
labResuts.value = [];
highlightRowIndex.value = -1;
}
};
const clearSearch = () => {
searchText.value = ''
searchQuery()
}
// 多条件查询
const handleQueryResult = (params: QueryParams) => {
// console.log('查询参数:', params);
// 复制原始列表作为筛选基础
let filteredList = [...originalLabPatList.value];
// 遍历所有查询参数,进行筛选
Object.entries(params).forEach(([key, value]) => {
filteredList = filteredList.filter((item: any) => {
if (value == 0) {
return item[key] == null;
} else {
return item[key] == value;
}
});
});
labPatList.value = filteredList;
getTotals();
if (filteredList.length > 0) {
highlightRowIndex.value = 0;
const firstRow = filteredList[0];
labPatListRef.value.setCurrentRow(firstRow);
selectJob(firstRow);
} else {
labPat.value = {};
labResuts.value = [];
highlightRowIndex.value = -1;
}
};
// 处理重置
const handleReset = () => {
labPatList.value = [...originalLabPatList.value];
getTotals();
}
const previewHandle = (url: string) => {
if (!url) return ElMessage.warning('未查询到文件')
pdfUrl.value = `data:application/pdf;base64,${url}`
previewShow.value = true
}
const handleinstrGroupChange = (val: any) => {
queryParams.value.yq = ''
// labResuts.value = []
// labPat.value = {}
getYqConfig()
}
const mbStore = useCommonStore();
const yqHandleChange = (val: any) => {
labResuts.value = []
labPat.value = {}
mbStore.setLxsrList([]);
getSysList()
fetchlabPatList()
}
const instrGroupFields = ref([
{ prop: 'zddh', label: '代号', width: 80, enablePinyinSearch: true },
{ prop: 'zdmc', label: '名称', width: 150, enablePinyinSearch: true },
])
// 部门
const loadinstrGroupOptions = () => {
getComDicts({ zdlb: 'GROUP' }).then((res: any) => {
instrGroupOptions.value = res.data
instrGroupOptions.value.push({ zddh: 'ALL', zdmc: '所有仪器' })
queryParams.value.instrGroup = '02'
queryParams.value.yq = '46'
getYqConfig()
});
}
const yqFields = ref([
{ prop: 'yq', label: '代号', width: 80, enablePinyinSearch: true },
{ prop: 'yqmc', label: '名称', width: 150, enablePinyinSearch: true },
])
// 仪器
const getYqConfig = () => {
getGroupInstrdList({ lisgroup: queryParams.value.instrGroup })
.then((res: any) => {
if (res.code == 0) {
instrOptions.value = res.data.map((item: any) => ({
yq: item.yq.trim(),
yqmc: item.yqmc
}))
}
})
}
const resultsHandle = (flag: boolean) => {
if (flag) {
fetchLabPat();
}
fetchLabResults()
}
const highlightRowIndex = ref(-1)
const labPatListRef = ref()
//样本列表点击切换样本信息,同步载入左边样本信息表单和中间结果表单
const selectJob = (job: any) => {
lastRow.value = job;
// 复制选中的job数据到当前编辑状态
// Object.assign(labPat.value, {...job})
queryParams.value.jyrq = job.jyrq;
queryParams.value.yq = job.yq.trim();
queryParams.value.ybh = job.ybh;
fetchLabPat();
fetchLabResults();
highlightRowIndex.value = labPatList.value.findIndex((item: any) => item.ybh == queryParams.value.ybh);
}
const labResuts = ref([])
//载入样本结果表单(中间labresult.vue组件)
const fetchLabResults = async () => {
queryLabResults({ jyrq: queryParams.value.jyrq, yq: queryParams.value.yq, ybh: queryParams.value.ybh }).then((response: any) => {
labResuts.value = response.data;
})
}
//载入样本编辑表单(左边labpat.vue组件)
const fetchLabPat = () => {
queryLabPat(queryParams.value).then((response: any) => {
labPat.value = response.data;
})
}
const lastRow: any = ref({})
// 批量扫码ybh同步样本编号
const ybhChangeTb = (val: string) => {
console.log('val==>', val);
queryParams.value.ybh = val
handleQuery();
}
const handleQuery = () => {
if (!labPatList.value.length) return
if (!queryParams.value.ybh) {
highlightRowIndex.value = -1;
ElMessage.warning('请输入样本编号')
queryParams.value.ybh = lastRow.value.ybh
return;
}
queryParams.value = { ...queryParams.value };
// 查找匹配的行
const matchedIndex = labPatList.value.findIndex((item: any) => item.ybh == queryParams.value.ybh);
const row = labPatList.value[matchedIndex]
if (matchedIndex !== -1) {
highlightRowIndex.value = matchedIndex;
// 等待DOM更新后滚动到目标行
nextTick(() => {
labPatListRef.value.setCurrentRow(row);
});
selectJob(row);
} else {
handleCreate()
highlightRowIndex.value = -1;
}
}
// 创建新样本
const handleCreate = () => {
loadDefault(queryParams.value).then((response: any) => {
if (response.code == 0) {
labPat.value = response.data;
labPat.value.id = `temp_${Date.now()}_${Math.random().toString(36).substr(2, 8)}`;
labResuts.value = []
labPatListRef.value?.clearCurrentRow();
fetchLabResults()
}
});
}
// 删除样本
const delSampleHd = () => {
if (lastRow.value.jgbz == '2') return ElMessage.warning('标本已审核,不能删除!')
const data = {
jyrq: lastRow.value.jyrq,
yq: lastRow.value.yq.trim(),
ybh: lastRow.value.ybh,
}
ElMessageBox.confirm(
`确定删除当前${lastRow.value.ybh}号标本?`,
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(() => {
delSample(data).then((res: any) => {
if (res.code == 0) {
ElMessage.success('删除成功')
labPatList.value = labPatList.value.filter((item: any) => item.ybh != lastRow.value.ybh)
getTotals()
// 删除跳当前页面下一个样本
// handleNext()
if (highlightRowIndex.value > labPatList.value.length - 1) {
highlightRowIndex.value--;
}
const row = labPatList.value[highlightRowIndex.value];
labPatListRef.value.setCurrentRow(row);
selectJob(row);
}
})
})
.catch(() => {
})
}
// 上一个样本
const handlePrev = () => {
if (!lbFlag.value) {
const ybh = getPreviousNumber(queryParams.value.ybh) as string;
queryParams.value.ybh = ybh;
const index = labPatList.value.findIndex((item: any) => item.ybh == ybh)
if (index !== -1) {
highlightRowIndex.value = index;
const row = labPatList.value[index];
labPatListRef.value.setCurrentRow(row);
selectJob(row);
} else {
handleCreate();
}
} else {
if (highlightRowIndex.value > 0) {
highlightRowIndex.value--;
const row = labPatList.value[highlightRowIndex.value];
labPatListRef.value.setCurrentRow(row);
selectJob(row);
}
}
}
// 下一个样本
const handleNext = () => {
if (!lbFlag.value) {
const ybh = getNextNumber(queryParams.value.ybh) as string;
queryParams.value.ybh = ybh;
const index = labPatList.value.findIndex((item: any) => item.ybh == ybh)
if (index !== -1) {
highlightRowIndex.value = index;
const row = labPatList.value[index];
labPatListRef.value.setCurrentRow(row);
selectJob(row);
} else {
handleCreate();
}
} else {
if (highlightRowIndex.value < labPatList.value.length - 1) {
highlightRowIndex.value++;
const row = labPatList.value[highlightRowIndex.value];
labPatListRef.value.setCurrentRow(row);
selectJob(row);
} else {
queryParams.value.ybh = getNextNumber(queryParams.value.ybh) as string;
handleCreate();
}
}
}
// 最后一个样本
const handleLast = () => {
queryParams.value.ybh = labPatList.value[labPatList.value.length - 1].ybh;
handleNext();
}
const batchRef = ref()
// 批量扫码
const handleBatchScan = () => {
batchRef.value.open()
}
// 改变样本列表中样本状态
const changeStatus = (updatedPat: any, flag: boolean) => {
queryLabPat(queryParams.value).then((response: any) => {
if (response.code == 0) {
labPat.value = response.data;
const index = labPatList.value.findIndex((item: any) => item.ybh == updatedPat.ybh);
if (index !== -1) {
labPatList.value = labPatList.value.map((item, i) =>
i === index ? { ...labPat.value } : item
);
highlightRowIndex.value = index;
nextTick(() => {
labPatListRef.value.setCurrentRow(labPat.value);
});
// 条码号输入后操作
if (flag) {
setTimeout(() => { handleNext() }, 10)
} else {
fetchLabResults()
}
} else {
// 条码号输入后操作
if (flag) {
setTimeout(() => { handleNext() }, 10)
} else {
fetchLabResults()
}
labPatList.value.push(labPat.value)
setTimeout(() => {
nextTick(() => {
labPatListRef.value.setCurrentRow(labPat.value);
});
}, 10)
getTotals()
}
}
});
};
//样本列表检索时刷新列表信息
const searchJobs = async (text: string, page: number, size: number) => {
fetchlabPatList()
}
//载入样本列表(右边labpatlist.vue组件)
const fetchlabPatList = () => {
if (!queryParams.value.jyrq) {
queryParams.value.jyrq = lastRow.value.jyrq
return ElMessage.warning('请选择检验日期')
}
loading.value = true;
queryLabPatList(queryParams.value).then((res: any) => {
if (res.code == 0) {
labPatList.value = res.data;
originalLabPatList.value = res.data
loading.value = false;
if (res.data.length > 0) {
highlightRowIndex.value = 0;
setTimeout(() => { labPatListRef.value.setCurrentRow(res.data[0]); }, 100)
selectJob(res.data[0])
getTotals()
} else {
ybs.value = 0
ws.value = 0
wjs.value = 0
}
}
})
}
// 计算样本数
const getTotals = () => {
ybs.value = labPatList.value.length
ws.value = labPatList.value.filter((item: any) => item.jgbz != 2).length
wjs.value = labPatList.value.filter((item: any) => item.alarmflag == 1).length
}
loadinstrGroupOptions()
fetchlabPatList()
interface DictData {
PT?: Array<any>;
AU?: Array<any>;
SX?: Array<any>;
DP?: Array<any>;
BT?: Array<any>;
SRD?: Array<any>;
HOS?: Array<any>;
ST?: Array<any>;
[key: string]: any[] | undefined; // 添加索引签名以支持动态访问
}
// 字典数据存储
const dictData = ref<DictData>({});
const labInputcolList = ref([])
const resultConfig = ref([])
// 获取参数配置
const getSysList = () => {
instrdconfig({ yq: queryParams.value.yq }).then((res: any) => {
if (res.code == 0) {
labInputcolList.value = res.data.labInputcolList.filter((item: any) => item.mrts != "年龄单位" && item.mrts != "!")
resultConfig.value = res.data.ResultConfig
}
});
mbStore.setLxsrList([]);
}
getSysList()
const userList = ref([]);
onMounted(async () => {
const data = { status: 0, del_flag: 0, pageSize: 1000, pageNum: 1 }
listUser(data).then((res: any) => {
userList.value = res.rows;
});
// 加载病人来源字典
const dictRefs = await comDict('PT', 'AU', 'SX', 'DP', 'BT', 'SRD', 'HOS', 'ST');
// 从 ref 中获取实际数据
dictData.value = {
PT: toRaw(dictRefs.PT.value) || [],
AU: toRaw(dictRefs.AU.value) || [],
SX: toRaw(dictRefs.SX.value) || [],
DP: toRaw(dictRefs.DP.value) || [],
BT: toRaw(dictRefs.BT.value) || [],
SRD: toRaw(dictRefs.SRD.value) || [],
HOS: toRaw(dictRefs.HOS.value) || [],
ST: toRaw(dictRefs.ST.value) || [],
};
// 初始化Socket
initSocket()
});
const initSocket = () => {
initWebSocket({
fullUrl: `ws://47.97.125.165:8904/ws/instr/${mbStore.fingerprint}`,
onOpen: () => {
console.log('WebSocket连接成功');
},
onMessage: (data) => {
console.log(`收到消息: `, JSON.parse(data));
},
onError: (error) => {
console.log(`连接错误: ${error.type}`);
},
onClose: () => {
console.log('WebSocket连接已关闭');
},
reconnectInterval: 3000 // 重连间隔(毫秒)
});
}
// 页面卸载时清理
onUnmounted(() => {
closeWebSocket();
});
// 发送消息
const sendMessage = () => {
const isSuccess = sendWebSocketMessage({ yq: queryParams.value.yq });
if (isSuccess) {
console.log(`发送消息`);
} else {
console.log('发送失败:连接未建立');
}
};
</script>
<style scoped lang="scss">
.app-container {
background: #F0F3F8;
}
.box-shadow {
box-shadow: 2px 2px 4px 0px rgba(206, 217, 234, 0.7);
border-radius: .75rem;
padding: .5rem;
background-color: #fff;
.flex-between {
margin-bottom: .5rem;
}
}
.right_box {
height: calc(90vh - 3.4rem);
}
.compact-form {
border-bottom: 1px solid #E1E9F7;
margin-bottom: 0.5rem;
.el-form-item--default {
margin-bottom: .3125rem !important;
}
.el-form-item--small {
margin-bottom: .3125rem !important;
}
}
.card-content {
max-height: 28.125rem;
/* 设置最大高度 */
overflow-y: auto;
/* 超出高度时显示垂直滚动条 */
padding: .3125rem;
/* 保持与卡片默认一致的内边距 */
}
.button-group {
display: flex;
justify-content: space-around;
font-family: SourceHanSansCN, SourceHanSansCN;
font-weight: 500;
font-size: 1rem;
color: #28BDBB;
// margin-top: 20px;
.text2 {
color: #1F33FF;
}
.text3 {
color: #FF0000;
}
}
</style>