550 lines
14 KiB
Vue
550 lines
14 KiB
Vue
/**
|
||
* @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>
|