feat: 抗菌药物规则 + 药品库存预警页面增强

- 抗菌药物规则页面增强(统计卡片/新增/详情/筛选)
- 药品库存预警页面增强(统计卡片/新增/补货申请/导出)
This commit is contained in:
2026-06-07 20:34:56 +08:00
parent 8b099d94df
commit aec389998d
2 changed files with 346 additions and 34 deletions

View File

@@ -1,22 +1,198 @@
<template>
<div class="app-container">
<el-form :model="q" :inline="true"><el-form-item label="药品编码"><el-input v-model="q.drugCode" clearable /></el-form-item>
<el-form-item><el-button type="primary" @click="getList">查询</el-button></el-form-item></el-form>
<el-table v-loading="loading" :data="list">
<el-table-column label="药品" prop="drugName" width="150" />
<el-table-column label="抗菌类别" prop="antibioticClass" width="120">
<template #default="s"><el-tag>{{ {RESTRICTED:'限制使用',NONRESTRICTED:'非限制使用',SPECIAL:'特殊使用'}[s.row.antibioticClass] }}</el-tag></template>
<div style="margin-bottom:16px;display:flex;justify-content:space-between;align-items:center">
<span style="font-size:18px;font-weight:bold">抗菌药物管理规则</span>
<div>
<el-button type="primary" @click="getList">刷新</el-button>
<el-button type="success" @click="showAdd = true">新增规则</el-button>
</div>
</div>
<!-- 统计卡片 -->
<el-row :gutter="16" style="margin-bottom:16px">
<el-col :span="6">
<el-card shadow="hover" :body-style="{padding:'12px'}">
<div style="text-align:center">
<div style="font-size:24px;font-weight:bold;color:#409eff">{{ stats.total || 0 }}</div>
<div style="color:#999">总规则数</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" :body-style="{padding:'12px'}">
<div style="text-align:center">
<div style="font-size:24px;font-weight:bold;color:#67c23a">{{ stats.nonRestricted || 0 }}</div>
<div style="color:#999">非限制使用</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" :body-style="{padding:'12px'}">
<div style="text-align:center">
<div style="font-size:24px;font-weight:bold;color:#e6a23c">{{ stats.restricted || 0 }}</div>
<div style="color:#999">限制使用</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" :body-style="{padding:'12px'}">
<div style="text-align:center">
<div style="font-size:24px;font-weight:bold;color:#f56c6c">{{ stats.special || 0 }}</div>
<div style="color:#999">特殊使用</div>
</div>
</el-card>
</el-col>
</el-row>
<!-- 筛选 -->
<el-form :model="q" :inline="true" style="margin-bottom:16px">
<el-form-item label="药品编码"><el-input v-model="q.drugCode" clearable placeholder="请输入药品编码" style="width:180px"/></el-form-item>
<el-form-item label="抗菌类别">
<el-select v-model="q.antibioticClass" clearable placeholder="请选择" style="width:140px">
<el-option label="非限制使用" value="NONRESTRICTED"/>
<el-option label="限制使用" value="RESTRICTED"/>
<el-option label="特殊使用" value="SPECIAL"/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="getList">查询</el-button>
<el-button @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 数据表格 -->
<el-table v-loading="loading" :data="list" border stripe>
<el-table-column label="药品编码" prop="drugCode" width="120"/>
<el-table-column label="药品名称" prop="drugName" width="160" show-overflow-tooltip/>
<el-table-column label="抗菌类别" prop="antibioticClass" width="120" align="center">
<template #default="s">
<el-tag :type="s.row.antibioticClass==='SPECIAL'?'danger':s.row.antibioticClass==='RESTRICTED'?'warning':'success'" size="small">
{{ {RESTRICTED:'限制使用',NONRESTRICTED:'非限制使用',SPECIAL:'特殊使用'}[s.row.antibioticClass] || s.row.antibioticClass }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="限制级别" prop="restrictionLevel" width="120" />
<el-table-column label="最大疗程(天)" prop="maxDurationDays" width="120" />
<el-table-column label="需审批" width="80">
<template #default="s"><el-tag :type="s.row.requireApproval?'danger':'success'">{{ s.row.requireApproval?'是':'否' }}</el-tag></template>
<el-table-column label="限制级别" prop="restrictionLevel" width="120" align="center"/>
<el-table-column label="最大疗程(天)" prop="maxDurationDays" width="120" align="center"/>
<el-table-column label="需审批" width="80" align="center">
<template #default="s">
<el-tag :type="s.row.requireApproval?'danger':'success'" size="small">
{{ s.row.requireApproval?'是':'否' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="适应症" prop="indications" min-width="150" show-overflow-tooltip/>
<el-table-column label="禁忌症" prop="contraindications" min-width="150" show-overflow-tooltip/>
<el-table-column label="操作" width="100" fixed="right">
<template #default="s">
<el-button link type="primary" @click="handleDetail(s.row)">详情</el-button>
</template>
</el-table-column>
</el-table>
<!-- 新增弹窗 -->
<el-dialog title="新增抗菌药物规则" v-model="showAdd" width="600px" append-to-body>
<el-form :model="formData" label-width="110px">
<el-form-item label="药品编码" required><el-input v-model="formData.drugCode" placeholder="请输入药品编码"/></el-form-item>
<el-form-item label="药品名称" required><el-input v-model="formData.drugName" placeholder="请输入药品名称"/></el-form-item>
<el-form-item label="抗菌类别" required>
<el-select v-model="formData.antibioticClass" placeholder="请选择">
<el-option label="非限制使用" value="NONRESTRICTED"/>
<el-option label="限制使用" value="RESTRICTED"/>
<el-option label="特殊使用" value="SPECIAL"/>
</el-select>
</el-form-item>
<el-form-item label="限制级别"><el-input v-model="formData.restrictionLevel" placeholder="如: 副主任医师以上"/></el-form-item>
<el-form-item label="最大疗程(天)"><el-input-number v-model="formData.maxDurationDays" :min="1" :max="365"/></el-form-item>
<el-form-item label="需审批">
<el-switch v-model="formData.requireApproval"/>
</el-form-item>
<el-form-item label="适应症"><el-input v-model="formData.indications" type="textarea" :rows="2" placeholder="请输入适应症"/></el-form-item>
<el-form-item label="禁忌症"><el-input v-model="formData.contraindications" type="textarea" :rows="2" placeholder="请输入禁忌症"/></el-form-item>
</el-form>
<template #footer>
<el-button @click="showAdd = false">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
</template>
</el-dialog>
<!-- 详情弹窗 -->
<el-dialog title="抗菌药物规则详情" v-model="detailVisible" width="600px" append-to-body>
<el-descriptions :column="2" border>
<el-descriptions-item label="药品编码">{{ detailData.drugCode }}</el-descriptions-item>
<el-descriptions-item label="药品名称">{{ detailData.drugName }}</el-descriptions-item>
<el-descriptions-item label="抗菌类别">
<el-tag :type="detailData.antibioticClass==='SPECIAL'?'danger':detailData.antibioticClass==='RESTRICTED'?'warning':'success'">
{{ {RESTRICTED:'限制使用',NONRESTRICTED:'非限制使用',SPECIAL:'特殊使用'}[detailData.antibioticClass] }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="限制级别">{{ detailData.restrictionLevel }}</el-descriptions-item>
<el-descriptions-item label="最大疗程">{{ detailData.maxDurationDays }}</el-descriptions-item>
<el-descriptions-item label="需审批">{{ detailData.requireApproval ? '是' : '否' }}</el-descriptions-item>
<el-descriptions-item label="适应症" :span="2">{{ detailData.indications || '-' }}</el-descriptions-item>
<el-descriptions-item label="禁忌症" :span="2">{{ detailData.contraindications || '-' }}</el-descriptions-item>
</el-descriptions>
<template #footer><el-button @click="detailVisible = false">关闭</el-button></template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'; import { getRules } from '@/api/antibiotic'
const loading = ref(false); const list = ref([]); const q = reactive({ drugCode: '' })
const getList = async () => { if (!q.drugCode) return; loading.value = true; const r = await getRules(q.drugCode); list.value = r.data || []; loading.value = false }
import {ref, reactive, onMounted} from 'vue'
import {ElMessage} from 'element-plus'
import {getRules, getStatistics, addRule} from '@/api/antibiotic'
const loading = ref(false)
const list = ref([])
const showAdd = ref(false)
const detailVisible = ref(false)
const detailData = ref({})
const stats = ref({total:0, nonRestricted:0, restricted:0, special:0})
const q = reactive({drugCode:'', antibioticClass:''})
const formData = reactive({
drugCode:'', drugName:'', antibioticClass:'NONRESTRICTED', restrictionLevel:'',
maxDurationDays:7, requireApproval:false, indications:'', contraindications:''
})
async function getList() {
loading.value = true
try {
if (q.drugCode) {
const r = await getRules(q.drugCode)
list.value = r.data || []
} else {
// Load all rules
const r = await getStatistics()
list.value = r.data?.rules || []
}
// Calculate stats
let nonRestricted = 0, restricted = 0, special = 0
list.value.forEach(row => {
if (row.antibioticClass === 'NONRESTRICTED') nonRestricted++
else if (row.antibioticClass === 'RESTRICTED') restricted++
else if (row.antibioticClass === 'SPECIAL') special++
})
stats.value = {total: list.value.length, nonRestricted, restricted, special}
} finally { loading.value = false }
}
function resetQuery() {
q.drugCode = ''
q.antibioticClass = ''
getList()
}
function handleDetail(row) {
detailData.value = row
detailVisible.value = true
}
async function submitForm() {
await addRule(formData)
ElMessage.success('新增成功')
showAdd.value = false
getList()
}
onMounted(() => getList())
</script>

View File

@@ -1,31 +1,167 @@
<template>
<div style="padding:16px">
<div style="margin-bottom:16px"><span style="font-size:18px;font-weight:bold">药品库存预警</span></div>
<el-card shadow="never" style="margin-bottom:16px">
<div style="display:flex;gap:40px;text-align:center">
<div><div style="font-size:28px;font-weight:bold;color:#e6a23c">{{ stats.low||0 }}</div><div>低库存</div></div>
<div><div style="font-size:28px;font-weight:bold;color:#f56c6c">{{ stats.out||0 }}</div><div>缺货</div></div>
<div style="margin-bottom:16px;display:flex;justify-content:space-between;align-items:center">
<span style="font-size:18px;font-weight:bold">药品库存预警管理</span>
<div>
<el-button type="primary" @click="loadData">刷新</el-button>
<el-button type="success" @click="showAdd = true">新增预警</el-button>
<el-button type="warning" @click="exportReport">导出报告</el-button>
</div>
</el-card>
<div style="margin-bottom:12px"><el-button type="success" @click="showAdd=true">新增预警</el-button></div>
<el-table :data="alertData" border stripe>
<el-table-column prop="drugName" label="药品" min-width="150"/>
<el-table-column prop="drugSpec" label="规格" width="100"/>
<el-table-column prop="currentStock" label="库存" width="80" align="center"/>
<el-table-column prop="minStock" label="最低" width="80" align="center"/>
<el-table-column prop="alertLevel" label="级别" width="100">
<template #default="{row}"><el-tag :type="row.alertLevel==='OUT'?'danger':'warning'" size="small">{{ row.alertLevel==='OUT'?'缺货':'低库存' }}</el-tag></template>
</div>
<!-- 统计卡片 -->
<el-row :gutter="16" style="margin-bottom:16px">
<el-col :span="4" v-for="item in statCards" :key="item.label">
<el-card shadow="hover" :body-style="{padding:'10px'}">
<div style="text-align:center">
<div style="font-size:20px;font-weight:bold" :style="{color:item.color}">{{ item.value }}</div>
<div style="font-size:12px;color:#999">{{ item.label }}</div>
</div>
</el-card>
</el-col>
</el-row>
<!-- 筛选 -->
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap">
<el-select v-model="q.alertLevel" placeholder="预警级别" clearable style="width:120px">
<el-option label="缺货" value="OUT"/>
<el-option label="低库存" value="LOW"/>
<el-option label="近效期" value="EXPIRING"/>
</el-select>
<el-select v-model="q.drugCategory" placeholder="药品类别" clearable style="width:140px">
<el-option label="西药" value="WESTERN"/>
<el-option label="中成药" value="CHINESE"/>
<el-option label="中草药" value="HERBAL"/>
<el-option label="生物制品" value="BIOLOGICAL"/>
</el-select>
<el-button type="primary" @click="loadData">查询</el-button>
<el-button @click="resetQuery">重置</el-button>
</div>
<!-- 数据表格 -->
<el-table :data="alertData" border stripe v-loading="loading">
<el-table-column prop="drugCode" label="药品编码" width="120"/>
<el-table-column prop="drugName" label="药品名称" min-width="160" show-overflow-tooltip/>
<el-table-column prop="drugSpec" label="规格" width="120"/>
<el-table-column prop="currentStock" label="当前库存" width="100" align="center">
<template #default="{row}">
<span :style="{color: row.currentStock <= 0 ? '#F56C6C' : row.currentStock < row.minStock ? '#E6A23C' : '#67C23A', fontWeight:'bold'}">
{{ row.currentStock }}
</span>
</template>
</el-table-column>
<el-table-column prop="minStock" label="最低库存" width="100" align="center"/>
<el-table-column prop="maxStock" label="最高库存" width="100" align="center"/>
<el-table-column prop="alertLevel" label="预警级别" width="100" align="center">
<template #default="{row}">
<el-tag :type="row.alertLevel==='OUT'?'danger':row.alertLevel==='LOW'?'warning':'info'" size="small">
{{ row.alertLevel==='OUT'?'缺货':row.alertLevel==='LOW'?'低库存':row.alertLevel==='EXPIRING'?'近效期':'正常' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="supplierName" label="供应商" width="140"/>
<el-table-column prop="lastRestockDate" label="上次进货" width="120"/>
<el-table-column prop="expiryDate" label="有效期至" width="120"/>
<el-table-column label="操作" width="120" fixed="right">
<template #default="{row}">
<el-button link type="primary" @click="handleOrder(row)">补货申请</el-button>
</template>
</el-table-column>
<el-table-column prop="supplierName" label="供应商" width="120"/>
</el-table>
<el-pagination style="margin-top:12px;justify-content:flex-end" v-model:current-page="q.pageNo" v-model:page-size="q.pageSize" :total="total" layout="total, sizes, prev, pager, next, jumper" @size-change="loadData" @current-change="loadData"/>
<!-- 新增弹窗 -->
<el-dialog title="新增药品库存预警" v-model="showAdd" width="600px" append-to-body>
<el-form :model="formData" label-width="110px">
<el-form-item label="药品编码" required><el-input v-model="formData.drugCode" placeholder="请输入药品编码"/></el-form-item>
<el-form-item label="药品名称" required><el-input v-model="formData.drugName" placeholder="请输入药品名称"/></el-form-item>
<el-form-item label="规格"><el-input v-model="formData.drugSpec" placeholder="如: 0.5g*24片"/></el-form-item>
<el-form-item label="当前库存"><el-input-number v-model="formData.currentStock" :min="0"/></el-form-item>
<el-form-item label="最低库存"><el-input-number v-model="formData.minStock" :min="0"/></el-form-item>
<el-form-item label="最高库存"><el-input-number v-model="formData.maxStock" :min="0"/></el-form-item>
<el-form-item label="预警级别">
<el-select v-model="formData.alertLevel" placeholder="请选择">
<el-option label="缺货" value="OUT"/>
<el-option label="低库存" value="LOW"/>
<el-option label="近效期" value="EXPIRING"/>
</el-select>
</el-form-item>
<el-form-item label="供应商"><el-input v-model="formData.supplierName" placeholder="请输入供应商"/></el-form-item>
</el-form>
<template #footer>
<el-button @click="showAdd = false">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import {ref,onMounted} from 'vue'
import {ref, reactive, onMounted} from 'vue'
import {ElMessage} from 'element-plus'
import {getAlertPage,addAlert,getAlertStats} from './api'
const alertData=ref([]),stats=ref({})
const showAdd=ref(false)
const loadData=async()=>{const [a,s]=await Promise.all([getAlertPage({pageNo:1,pageSize:50}),getAlertStats()]);alertData.value=a.data?.records||[];stats.value=s.data||{}}
onMounted(()=>loadData())
import {getAlertPage, addAlert, getAlertStats} from './api'
const loading = ref(false)
const alertData = ref([])
const total = ref(0)
const showAdd = ref(false)
const statCards = ref([
{label:'总预警数', value:0, color:'#409eff'},
{label:'缺货', value:0, color:'#f56c6c'},
{label:'低库存', value:0, color:'#e6a23c'},
{label:'近效期', value:0, color:'#909399'},
{label:'供应商数', value:0, color:'#67c23a'},
{label:'待补货', value:0, color:'#409eff'}
])
const q = ref({pageNo:1, pageSize:20, alertLevel:'', drugCategory:''})
const formData = reactive({
drugCode:'', drugName:'', drugSpec:'', currentStock:0, minStock:0,
maxStock:100, alertLevel:'LOW', supplierName:''
})
async function loadData() {
loading.value = true
try {
const [a, s] = await Promise.all([getAlertPage(q.value), getAlertStats()])
alertData.value = a.data?.records || []
total.value = a.data?.total || 0
// Stats
let out = 0, low = 0, expiring = 0, supplierSet = new Set()
alertData.value.forEach(row => {
if (row.alertLevel === 'OUT') out++
else if (row.alertLevel === 'LOW') low++
else if (row.alertLevel === 'EXPIRING') expiring++
supplierSet.add(row.supplierName)
})
statCards.value[0].value = total.value
statCards.value[1].value = out
statCards.value[2].value = low
statCards.value[3].value = expiring
statCards.value[4].value = supplierSet.size
statCards.value[5].value = s.data?.pendingOrders || 0
} finally { loading.value = false }
}
function resetQuery() {
q.value = {pageNo:1, pageSize:20, alertLevel:'', drugCategory:''}
loadData()
}
async function submitForm() {
await addAlert(formData)
ElMessage.success('新增成功')
showAdd.value = false
loadData()
}
function handleOrder(row) {
ElMessage.info('补货申请: ' + row.drugName)
}
function exportReport() { ElMessage.info('导出功能开发中') }
onMounted(() => loadData())
</script>