优化前端布局
This commit is contained in:
@@ -454,7 +454,7 @@ INSERT INTO `menu` (`id`, `parent_id`, `name`, `type`, `path`, `component`, `ico
|
||||
|
||||
-- ===== 药品管理(目录) =====
|
||||
INSERT INTO `menu` (`id`, `parent_id`, `name`, `type`, `path`, `component`, `icon`, `permission_code`, `sort_order`) VALUES
|
||||
(21, 0, '药品管理', 1, NULL, NULL, 'MedicineBox', NULL, 2),
|
||||
(21, 0, '药品管理', 1, NULL, NULL, 'Goods', NULL, 2),
|
||||
(22, 21, '药品列表', 2, '/medicine/list', 'views/medicine/MedicineList', NULL, 'medicine:list', 21),
|
||||
(23, 22, '药品查询', 3, NULL, NULL, NULL, 'medicine:query', 211),
|
||||
(24, 22, '新增药品', 3, NULL, NULL, NULL, 'medicine:add', 212),
|
||||
|
||||
@@ -8,89 +8,157 @@ html, body, #app {
|
||||
height: 100%;
|
||||
font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB',
|
||||
'Microsoft YaHei', Arial, sans-serif;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
// 页面通用样式
|
||||
// 页面容器(卡片式)
|
||||
.page-container {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
min-height: calc(100vh - 140px);
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
// 页面标题
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 4px;
|
||||
height: 18px;
|
||||
background: #409eff;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索区
|
||||
.search-bar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
padding: 16px;
|
||||
background: #fafafa;
|
||||
border-radius: 4px;
|
||||
background: #f5f7fa;
|
||||
border-radius: 8px;
|
||||
|
||||
.el-input, .el-select, .el-cascader, .el-date-picker {
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
// 工具栏
|
||||
.tool-bar {
|
||||
margin-bottom: 16px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
// 表格操作按钮
|
||||
// 操作按钮组
|
||||
.table-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
// 弹窗表单
|
||||
.dialog-form {
|
||||
.el-form-item {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.el-input, .el-select, .el-cascader {
|
||||
width: 100%;
|
||||
}
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
// 统计卡片
|
||||
.stat-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 16px;
|
||||
margin-bottom: 20px;
|
||||
gap: 20px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
|
||||
gap: 20px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 12px;
|
||||
border-radius: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 28px;
|
||||
color: #fff;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.stat-info {
|
||||
.stat-label {
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.stat-value {
|
||||
font-size: 28px;
|
||||
font-size: 30px;
|
||||
font-weight: 700;
|
||||
color: #303133;
|
||||
line-height: 1.2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 区域卡片
|
||||
.section-card {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
// 对话框表单
|
||||
.dialog-form {
|
||||
.el-form-item {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.el-input, .el-select, .el-cascader, .el-input-number {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
// 状态标签增强
|
||||
.status-dot {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
// 响应式
|
||||
@media (max-width: 1400px) {
|
||||
.stat-cards { grid-template-columns: repeat(2, 1fr); }
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.stat-cards { grid-template-columns: 1fr; }
|
||||
.page-container { padding: 16px; }
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user