2026-09-16 18:14:31 +08:00

550 lines
14 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.

/**
* @file index.vue 抗生素折点范围字典
* @author: w
* @since: 2026-09-16
*/
<template>
<div class="app-container">
<div class="unified-panel">
<div class="btn-bar">
<el-button type="primary" icon="Search" @click="search">查询</el-button>
<el-button type="primary" icon="Check" @click="saveBreakpoints">保存</el-button>
<el-button type="warning" icon="Refresh" @click="reset">重置</el-button>
</div>
</div>
<div class="content-container">
<!-- 左栏:菌属树 -->
<div class="panel left-panel">
<div class="panel-header">菌属</div>
<div class="panel-content">
<el-input v-model="treeFilter" placeholder="搜索菌属" size="small" clearable style="padding: 4px;" />
<el-tree
ref="treeRef"
:data="germClassTree"
:props="{ label: 'label', children: 'children' }"
node-key="zddh"
highlight-current
default-expand-all
:filter-node-method="filterNode"
@node-click="handleTreeNodeClick"
>
<template #default="{ node, data }">
<span class="tree-node">
<span class="tree-code">{{ data.zddh }}</span>
<span class="tree-name">{{ data.zdmc }}</span>
</span>
</template>
</el-tree>
</div>
</div>
<!-- 中栏:折点范围编辑 -->
<div class="panel middle-panel">
<div class="panel-header">折点范围编辑</div>
<div class="panel-content">
<el-table
:data="breakpointList"
border
size="mini"
height="100%"
style="width: 100%"
>
<el-table-column prop="ywdh" label="药物代号" width="90" fixed />
<el-table-column label="抗生素" width="130" fixed show-overflow-tooltip>
<template #default="scope">{{ getMedName(scope.row.ywdh) }}</template>
</el-table-column>
<el-table-column label="S(MIC<=)" width="75">
<template #default="scope">
<el-input v-model="scope.row.mic1" size="small" />
</template>
</el-table-column>
<el-table-column label="R(MIC>=)" width="75">
<template #default="scope">
<el-input v-model="scope.row.mic2" size="small" />
</template>
</el-table-column>
<el-table-column prop="micref" label="MIC折点" width="130" show-overflow-tooltip />
<el-table-column label="S(KB>=)" width="75">
<template #default="scope">
<el-input v-model="scope.row.rad1" size="small" />
</template>
</el-table-column>
<el-table-column label="R(KB<=)" width="75">
<template #default="scope">
<el-input v-model="scope.row.rad2" size="small" />
</template>
</el-table-column>
<el-table-column prop="radref" label="KB折点" width="130" show-overflow-tooltip />
<el-table-column label="I=SDD" width="45" align="center">
<template #default="scope">
<el-checkbox v-model="scope.row.sddfalg" :true-value="1" :false-value="null" class="sdd-checkbox" />
</template>
</el-table-column>
<el-table-column prop="xh" label="序号" width="55" />
<el-table-column label="操作" width="65" fixed="right">
<template #default="scope">
<el-button type="danger" link size="small" @click="removeBreakpoint(scope.$index)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
<!-- 右栏:抗生素字典 -->
<div class="panel right-panel">
<div class="panel-header">抗生素字典</div>
<div class="panel-content right-content">
<el-input v-model="antibioticFilter" placeholder="搜索抗生素" size="small" clearable style="padding: 4px;" />
<el-table
:data="filteredAntibioticList"
border
size="mini"
height="100%"
style="width: 100%"
@row-dblclick="handleAddBreakpoint"
>
<el-table-column prop="ywdh" label="代号" width="85" show-overflow-tooltip />
<el-table-column prop="ywmc" label="药物名称" width="140" show-overflow-tooltip />
<el-table-column prop="zjf" label="简码" width="70" show-overflow-tooltip />
<el-table-column prop="yydh" label="英文代码" width="130" show-overflow-tooltip />
<el-table-column prop="zjmic" label="中介MIC" width="90" show-overflow-tooltip />
<el-table-column prop="nymic" label="尿MIC" width="85" show-overflow-tooltip />
<el-table-column prop="multimic" label="多级参数" width="75" show-overflow-tooltip />
<el-table-column prop="yf" label="用法" width="80" show-overflow-tooltip />
<el-table-column prop="xyfz" label="血药浓度" width="80" show-overflow-tooltip />
<el-table-column prop="seq" label="序号" width="55" show-overflow-tooltip />
</el-table>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, computed, watch } from 'vue'
import { ElMessage } from 'element-plus'
import {
getGermClassList,
getAntibioticList,
getBreakpointList,
batchSaveBreakpoint
} from '@/api/liswork/xtwh/AntibioticBreakpointApi'
const treeRef = ref(null)
const treeFilter = ref('')
const antibioticFilter = ref('')
const germClassList = ref([])
const antibioticList = ref([])
const breakpointList = ref([])
const currentGermClass = ref(null)
const loading = ref(false)
let rowWatchers = []
const germClassTree = computed(() => {
return germClassList.value.map(item => ({
...item,
label: `${item.zddh} ${item.zdmc}`
}))
})
const getMedName = (ywdh) => {
return antibioticList.value.find(m => m.ywdh === ywdh)?.ywmc || ''
}
const filteredAntibioticList = computed(() => {
const kw = antibioticFilter.value.trim().toLowerCase()
if (!kw) return antibioticList.value
return antibioticList.value.filter(m =>
m.ywmc?.toLowerCase().includes(kw) ||
m.ywdh?.toLowerCase().includes(kw) ||
m.zjf?.toLowerCase().includes(kw) ||
m.yydh?.toLowerCase().includes(kw)
)
})
const calcMicRef = (mic1, mic2) => {
const s = mic1 != null && mic1 !== '' ? Number(mic1) : null
const r = mic2 != null && mic2 !== '' ? Number(mic2) : null
if (s == null && r == null) return ''
if (s != null && r != null) {
const mid = buildMicMiddle(s, r)
return `<=${s}${mid ? ', ' + mid : ''}, >=${r}`
}
return s != null ? `<=${s}` : `>=${r}`
}
const calcRadRef = (rad1, rad2) => {
const s = rad1 != null && rad1 !== '' ? Number(rad1) : null
const r = rad2 != null && rad2 !== '' ? Number(rad2) : null
if (s == null && r == null) return ''
if (s != null && r != null) {
const mid = buildRadMiddle(s, r)
return `>=${r}${mid ? ', ' + mid : ''}, <=${s}`
}
return r != null ? `>=${r}` : `<=${s}`
}
const buildMicMiddle = (s, r) => {
if (r - s <= 1) return ''
if (Number.isInteger(s) && Number.isInteger(r)) {
return `${s + 1}-${r - 1}`
}
const vals = []
let v = s + 1
while (v < r) {
vals.push(Number.isInteger(v) ? v.toString() : v.toFixed(1))
v = Math.round((v + 1) * 10) / 10
}
return vals.join(',')
}
const buildRadMiddle = (s, r) => {
if (r - s <= 1) return ''
return `${s + 1}-${r - 1}`
}
const setupBreakpointWatchers = (row) => {
return [
watch(() => row.mic1, val => { row.micref = calcMicRef(val, row.mic2) }),
watch(() => row.mic2, val => { row.micref = calcMicRef(row.mic1, val) }),
watch(() => row.rad1, val => { row.radref = calcRadRef(val, row.rad2) }),
watch(() => row.rad2, val => { row.radref = calcRadRef(row.rad1, val) })
]
}
watch(treeFilter, val => {
treeRef.value?.filter(val)
})
const filterNode = (value, data) => {
if (!value) return true
return data.zdmc.includes(value) || data.zddh.toLowerCase().includes(value.toLowerCase())
}
const loadGermClassList = () => {
getGermClassList().then(res => {
germClassList.value = res.data || []
if (germClassList.value.length > 0) {
currentGermClass.value = germClassList.value[0]
loadBreakpoints()
}
})
}
const loadAntibioticList = () => {
getAntibioticList().then(res => {
antibioticList.value = res.data || []
})
}
const loadBreakpoints = () => {
if (!currentGermClass.value) return
loading.value = true
getBreakpointList({ germClassCode: currentGermClass.value.zddh }).then(res => {
rowWatchers.forEach(stop => stop())
rowWatchers = []
breakpointList.value = res.data || []
breakpointList.value.forEach(row => {
rowWatchers.push(...setupBreakpointWatchers(row))
})
}).finally(() => {
loading.value = false
})
}
const handleTreeNodeClick = (data) => {
currentGermClass.value = data
loadBreakpoints()
}
const handleAddBreakpoint = (row) => {
if (!currentGermClass.value) {
ElMessage.warning('请先选择菌属')
return
}
const exists = breakpointList.value.some(item => item.ywdh === row.ywdh)
if (exists) {
ElMessage.warning(`${row.ywmc} 已在折点列表中`)
return
}
const maxXh = breakpointList.value.reduce((max, item) => Math.max(max, item.xh || 0), 0)
const newRow = {
ywdh: row.ywdh,
mic1: null,
mic2: null,
rad1: null,
rad2: null,
micref: '',
radref: '',
xh: maxXh + 10,
sddfalg: null,
bz: null
}
breakpointList.value.push(newRow)
rowWatchers.push(...setupBreakpointWatchers(newRow))
}
const removeBreakpoint = (index) => {
breakpointList.value.splice(index, 1)
}
const search = () => {
loadGermClassList()
loadAntibioticList()
if (currentGermClass.value) {
loadBreakpoints()
}
}
const saveBreakpoints = () => {
if (!currentGermClass.value) {
ElMessage.warning('请先选择菌属')
return
}
if (breakpointList.value.length === 0) {
ElMessage.warning('没有可保存的数据')
return
}
const data = breakpointList.value.map(item => ({
yq: '_med_',
xmdh: currentGermClass.value.zddh,
ywzmc: '菌属',
ywdh: item.ywdh,
mic1: item.mic1 != null ? Number(item.mic1) : null,
mic2: item.mic2 != null ? Number(item.mic2) : null,
rad1: item.rad1 != null ? Number(item.rad1) : null,
rad2: item.rad2 != null ? Number(item.rad2) : null,
xh: item.xh,
micref: item.micref,
radref: item.radref,
sddfalg: item.sddfalg,
bz: item.bz
}))
batchSaveBreakpoint(data).then(() => {
ElMessage.success('保存成功')
loadBreakpoints()
}).catch(() => {
ElMessage.error('保存失败')
})
}
const reset = () => {
if (currentGermClass.value) {
loadBreakpoints()
}
}
onMounted(() => {
search()
})
</script>
<style scoped lang="scss">
.app-container {
display: flex;
flex-direction: column;
overflow: hidden;
background-color: #d5d5d5;
}
.unified-panel {
background: #e6f7fa;
border-right: 1px solid #d9e8fb;
border-bottom: 1px solid #d9e8fb;
border-left: 1px solid #d9e8fb;
border-radius: 0 0 8px 8px;
box-sizing: border-box;
overflow: hidden;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
margin-top: -10px;
}
.btn-bar {
padding: 8px 12px 8px 12px;
}
.content-container {
display: flex;
flex: 1;
gap: 5px;
padding: 10px;
overflow: hidden;
min-height: 0;
}
.panel {
display: flex;
flex-direction: column;
background: #fff;
border: 1px solid #d9e8fb;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.panel-header {
padding: 8px 12px;
background: #f0f8ff;
border-bottom: 1px solid #d9e8fb;
font-size: 14px;
font-weight: 600;
color: #3282F6;
font-family: SimSun, "宋体", serif;
}
.panel-content {
flex: 1;
overflow: hidden;
min-height: 0;
}
.left-panel {
width: 220px;
flex-shrink: 0;
}
.middle-panel {
flex: 1;
min-width: 400px;
}
.right-panel {
width: 560px;
flex-shrink: 0;
}
.right-content {
display: flex;
flex-direction: column;
:deep(.el-table) {
flex: 1;
min-height: 0;
}
}
// 表格样式
:deep(.el-table) {
--el-table-row-height: 20px;
--el-table-header-row-height: 26px;
}
:deep(.el-table__header-wrapper th) {
height: 26px !important;
min-height: 26px !important;
max-height: 26px !important;
line-height: 26px !important;
padding: 0 4px !important;
white-space: nowrap !important;
overflow: hidden;
text-overflow: ellipsis;
font-family: SimSun, "宋体", serif;
}
:deep(.el-table__body-wrapper td) {
height: 20px !important;
min-height: 20px !important;
max-height: 20px !important;
line-height: 20px !important;
padding: 0 !important;
font-family: SimSun, "宋体", serif;
}
:deep(.el-table__cell) {
font-family: SimSun, "宋体", serif;
}
:deep(.el-table__cell .cell) {
display: flex;
align-items: center;
height: 100%;
padding: 0 2px;
line-height: 20px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
:deep(.el-table__body-wrapper .el-input) {
width: 100%;
height: 100%;
}
:deep(.el-table__body-wrapper .el-input__wrapper) {
height: 100%;
box-shadow: none;
border-radius: 0;
margin: 0;
padding: 0 4px;
}
:deep(.el-table__body-wrapper .el-input__inner) {
height: 100%;
line-height: 20px;
padding: 0;
font-size: 12px;
}
.tree-node {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
font-family: SimSun, "宋体", serif;
}
.tree-code {
color: #3282F6;
font-weight: 600;
min-width: 36px;
}
.tree-name {
color: #333;
}
:deep(.el-tree) {
font-size: 12px;
}
:deep(.el-tree-node__content) {
height: 26px;
padding-left: 4px !important;
}
:deep(.el-tree-node__content:hover) {
background: #ecf5ff;
}
:deep(.el-tree-node.is-current > .el-tree-node__content) {
background: #409eff;
color: #fff;
font-weight: 600;
}
:deep(.el-tree-node.is-current > .el-tree-node__content .tree-code) {
color: #fff;
}
:deep(.el-tree-node.is-current > .el-tree-node__content .tree-name) {
color: #fff;
}
:deep(.sdd-checkbox) {
margin: 0 auto;
display: flex;
justify-content: center;
}
:deep(.sdd-checkbox .el-checkbox__input) {
transform: scale(1.15);
}
.right-panel :deep(.el-table__row) {
cursor: pointer;
}
</style>