优化前端布局

This commit is contained in:
2026-07-07 09:34:41 +08:00
parent 2d7cadfa2b
commit cc3c1aba96
14 changed files with 167 additions and 74 deletions

View File

@@ -60,6 +60,7 @@ onMounted(loadData)
<template>
<div class="page-container">
<div class="page-header"><span class="page-title">药品分类</span></div>
<div class="tool-bar">
<el-button type="primary" v-permission="'category:add'" :icon="Plus" @click="handleAdd(0)">+ 新增顶级分类</el-button>
</div>

View File

@@ -22,6 +22,7 @@ onMounted(loadData)
<template>
<div class="page-container">
<div class="page-header"><span class="page-title">客户管理</span></div>
<div class="search-bar">
<el-input v-model="query.name" placeholder="客户名称" clearable />
<el-button type="primary" :icon="Search" @click="loadData">搜索</el-button>

View File

@@ -3,8 +3,7 @@ import { ref, onMounted } from 'vue'
import { getStatistics, getExpiryWarning } from '@/api/dashboard'
import { getStockInList } from '@/api/stockIn'
import { getStockOutList } from '@/api/stockOut'
import { Goods, Box, DocumentChecked, Warning } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import { Goods, Box, DocumentChecked, Warning, Clock } from '@element-plus/icons-vue'
const statistics = ref({ medicineCount: 0, stockBatchCount: 0, pendingStockIn: 0, expiryWarningCount: 0 })
const expiryList = ref([])
@@ -12,10 +11,10 @@ const recentInList = ref([])
const recentOutList = ref([])
const statCards = [
{ key: 'medicineCount', label: '药品总数', color: '#409eff', icon: Goods },
{ key: 'stockBatchCount', label: '库存批次', color: '#67c23a', icon: Box },
{ key: 'pendingStockIn', label: '待审入库', color: '#e6a23c', icon: DocumentChecked },
{ key: 'expiryWarningCount', label: '效期预警', color: '#f56c6c', icon: Warning },
{ key: 'medicineCount', label: '药品总数', color: '#409eff', bg: 'linear-gradient(135deg, #409eff, #66b1ff)', icon: Goods },
{ key: 'stockBatchCount', label: '库存批次', color: '#67c23a', bg: 'linear-gradient(135deg, #67c23a, #95d475)', icon: Box },
{ key: 'pendingStockIn', label: '待审入库', color: '#e6a23c', bg: 'linear-gradient(135deg, #e6a23c, #f3d19e)', icon: DocumentChecked },
{ key: 'expiryWarningCount', label: '效期预警', color: '#f56c6c', bg: 'linear-gradient(135deg, #f56c6c, #fab6b6)', icon: Warning },
]
function expiryColor(date) {
@@ -30,22 +29,29 @@ onMounted(async () => {
try {
const [statRes, warnRes, inRes, outRes] = await Promise.allSettled([
getStatistics(), getExpiryWarning(),
getStockInList({ page: 1, size: 5 }),
getStockOutList({ page: 1, size: 5 }),
getStockInList({ page: 1, size: 4 }),
getStockOutList({ page: 1, size: 4 }),
])
if (statRes.status === 'fulfilled') statistics.value = statRes.value.data || {}
if (warnRes.status === 'fulfilled') expiryList.value = warnRes.value.data?.records || warnRes.value.data || []
if (inRes.status === 'fulfilled') recentInList.value = inRes.value.data?.records || []
if (outRes.status === 'fulfilled') recentOutList.value = outRes.value.data?.records || []
} catch { /* 后端未启动 */ }
} catch { /* */ }
})
</script>
<template>
<div>
<div class="page-header">
<span class="page-title">仪表盘</span>
<span style="color:#909399;font-size:13px">
<el-icon><Clock /></el-icon> {{ new Date().toLocaleDateString('zh-CN', { year:'numeric', month:'long', day:'numeric', weekday:'long' }) }}
</span>
</div>
<div class="stat-cards">
<div v-for="card in statCards" :key="card.key" class="stat-card">
<div class="stat-icon" :style="{ background: card.color }">
<div class="stat-icon" :style="{ background: card.bg }">
<el-icon><component :is="card.icon" /></el-icon>
</div>
<div class="stat-info">
@@ -55,53 +61,57 @@ onMounted(async () => {
</div>
</div>
<el-card class="section-card" header="效期预警90天内到期">
<el-table :data="expiryList" size="small" empty-text="暂无预警">
<el-table-column prop="medicineName" label="药品" min-width="160" />
<el-table-column prop="batchNo" label="批号" width="120" />
<el-table-column prop="quantity" label="库存" width="80" />
<el-table-column prop="expiryDate" label="有效期至" width="130">
<template #default="{ row }">
<el-tag :type="expiryColor(row.expiryDate)" size="small">{{ row.expiryDate }}</el-tag>
</template>
</el-table-column>
</el-table>
</el-card>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px">
<el-card header="最近入库">
<template v-if="recentInList.length">
<div v-for="item in recentInList" :key="item.id" class="record-item">
<span>{{ item.recordNo }}</span>
<span style="color:#909399;font-size:13px">{{ item.supplierName }}</span>
<el-tag :type="item.status===1?'success':item.status===0?'warning':'info'" size="small">
{{ item.status===1?'已入库':item.status===0?'待审核':'已取消' }}
</el-tag>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:20px">
<div class="section-card">
<h4 style="margin-bottom:12px;color:#e6a23c"><el-icon><Warning /></el-icon> 效期预警90天内到期</h4>
<el-table :data="expiryList" size="small" empty-text="暂无预警 ">
<el-table-column prop="medicineName" label="药品" min-width="140" />
<el-table-column prop="batchNo" label="批号" width="110" />
<el-table-column prop="quantity" label="库存" width="70" align="center" />
<el-table-column prop="expiryDate" label="有效期至" width="120" align="center">
<template #default="{ row }">
<el-tag :type="expiryColor(row.expiryDate)" size="small" effect="dark">{{ row.expiryDate }}</el-tag>
</template>
</el-table-column>
</el-table>
</div>
<div class="section-card">
<h4 style="margin-bottom:12px;color:#409eff"><el-icon><DocumentChecked /></el-icon> 最近业务</h4>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px">
<div>
<h5 style="margin-bottom:8px;font-size:13px;color:#909399">最近入库</h5>
<div v-if="recentInList.length">
<div v-for="item in recentInList" :key="item.id" class="record-item">
<span style="font-size:13px;font-weight:500">{{ item.recordNo }}</span>
<el-tag :type="item.status===1?'success':item.status===0?'warning':'info'" size="small" effect="plain">
{{ item.status===1?'已入库':item.status===0?'待审核':'已取消' }}
</el-tag>
</div>
</div>
<el-empty v-else description="暂无" :image-size="40" />
</div>
</template>
<el-empty v-else description="暂无数据" />
</el-card>
<el-card header="最近出库">
<template v-if="recentOutList.length">
<div v-for="item in recentOutList" :key="item.id" class="record-item">
<span>{{ item.recordNo }}</span>
<span style="color:#909399;font-size:13px">{{ item.customerName || '-' }}</span>
<el-tag :type="item.status===1?'success':item.status===0?'warning':'info'" size="small">
{{ item.status===1?'已出库':item.status===0?'待审核':'已取消' }}
</el-tag>
<div>
<h5 style="margin-bottom:8px;font-size:13px;color:#909399">最近出库</h5>
<div v-if="recentOutList.length">
<div v-for="item in recentOutList" :key="item.id" class="record-item">
<span style="font-size:13px;font-weight:500">{{ item.recordNo }}</span>
<el-tag :type="item.status===1?'success':item.status===0?'warning':'info'" size="small" effect="plain">
{{ item.status===1?'已出库':item.status===0?'待审核':'已取消' }}
</el-tag>
</div>
</div>
<el-empty v-else description="暂无" :image-size="40" />
</div>
</template>
<el-empty v-else description="暂无数据" />
</el-card>
</div>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.section-card { margin-bottom: 16px; }
.record-item {
display: flex; justify-content: space-between; align-items: center;
padding: 10px 0; border-bottom: 1px solid #f0f0f0;
padding: 8px 0; border-bottom: 1px dashed #ebeef5;
&:last-child { border-bottom: none; }
}
</style>

View File

@@ -94,6 +94,10 @@ onMounted(async () => {
<template>
<div class="page-container">
<div class="page-header">
<span class="page-title">药品管理</span>
<span style="color:#909399;font-size:13px"> {{ total }} 条记录</span>
</div>
<!-- 搜索栏 -->
<div class="search-bar">
<el-input v-model="query.name" placeholder="药品名称" clearable @keyup.enter="loadData" />

View File

@@ -73,6 +73,7 @@ onMounted(loadData)
<template>
<div class="page-container">
<div class="page-header"><span class="page-title">菜单管理</span></div>
<div class="tool-bar">
<el-button type="primary" v-permission="'menu:add'" :icon="Plus" @click="handleAdd(0)">新增顶级菜单</el-button>
</div>

View File

@@ -34,6 +34,7 @@ onMounted(loadData)
<template>
<div class="page-container">
<div class="page-header"><span class="page-title">角色管理</span></div>
<div class="tool-bar">
<el-button type="primary" v-permission="'role:add'" @click="handleAdd">+ 新增角色</el-button>
</div>

View File

@@ -26,6 +26,7 @@ onMounted(loadData)
<template>
<div class="page-container">
<div class="page-header"><span class="page-title">库存管理</span></div>
<div class="search-bar">
<el-input v-model="query.medicineName" placeholder="药品名称" clearable />
<el-input v-model="query.batchNo" placeholder="批号" clearable />

View File

@@ -54,6 +54,7 @@ onMounted(loadData)
<template>
<div class="page-container">
<div class="page-header"><span class="page-title">入库管理</span></div>
<div class="search-bar">
<el-input v-model="query.recordNo" placeholder="入库单号" clearable style="width:200px" @keyup.enter="loadData" />
<el-button type="primary" :icon="Search" @click="loadData">搜索</el-button>

View File

@@ -55,6 +55,7 @@ onMounted(loadData)
<template>
<div class="page-container">
<div class="page-header"><span class="page-title">出库管理</span></div>
<div class="search-bar">
<el-input v-model="query.recordNo" placeholder="出库单号" clearable style="width:200px" @keyup.enter="loadData" />
<el-button type="primary" :icon="Search" @click="loadData">搜索</el-button>

View File

@@ -26,6 +26,9 @@ onMounted(loadData)
<template>
<div class="page-container">
<div class="page-header">
<span class="page-title">供应商管理</span>
</div>
<div class="search-bar">
<el-input v-model="query.name" placeholder="供应商名称" clearable @keyup.enter="loadData" />
<el-button type="primary" :icon="Search" @click="loadData">搜索</el-button>

View File

@@ -28,6 +28,7 @@ onMounted(async () => { try { allRoles.value = (await getAllRoles()).data || []
<template>
<div class="page-container">
<div class="page-header"><span class="page-title">用户管理</span></div>
<div class="search-bar">
<el-input v-model="query.username" placeholder="用户名" clearable />
<el-button type="primary" :icon="Search" @click="loadData">搜索</el-button>