550 lines
14 KiB
Vue
Raw Normal View History

2026-09-16 18:14:31 +08:00
/**
* @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>