feat(menu): 优化菜单服务性能并新增医生排班功能

- 添加菜单缓存注解以提升查询性能
- 实现菜单完整路径计算优化,解决 N+1 查询问题
- 新增 selectAllMenus 方法供路径计算使用
- 添加今日医生排班查询功能
- 重构前端图标显示逻辑,使用 SVG 图标替代 Element 图标
- 添加前端菜单数据本地缓存机制
- 更新菜单管理界面的表单组件绑定方式
- 新增预约管理、门诊管理和药房管理路由配置
This commit is contained in:
2026-02-02 08:46:33 +08:00
parent 669d669422
commit 5534a71c7d
20 changed files with 1156 additions and 228 deletions

View File

@@ -59,9 +59,7 @@
@click="handleQuickAccess(func)"
>
<div class="quick-icon">
<el-icon :size="28" :color="func.iconColor">
<component :is="func.icon" />
</el-icon>
<svg-icon :icon-class="func.icon" :style="{ fontSize: '28px', color: func.iconColor }" />
</div>
<div class="quick-label">{{ func.label }}</div>
</div>
@@ -138,6 +136,7 @@ import { getHomeStatistics, getPendingEmrCount } from '@/api/home'
import { listTodo } from '@/api/workflow/task.js'
import { getCurrentUserConfig } from '@/api/system/userConfig'
import { listMenu, getMenuFullPath } from '@/api/system/menu'
import { getTodayDoctorScheduleList } from '@/api/appointmentmanage/doctorSchedule'
import { ElDivider } from 'element-plus'
import {
User,
@@ -220,6 +219,7 @@ import {
// 为别名单独导入
import { InfoFilled as InfoFilledIcon, QuestionFilled as QuestionFilledIcon, SuccessFilled } from '@element-plus/icons-vue'
import SvgIcon from '@/components/SvgIcon'
const userStore = useUserStore()
const router = useRouter()
@@ -400,8 +400,8 @@ const convertMenuIdsToQuickAccess = async (menuIds) => {
return {
key: menuItem.menuId,
label: menuItem.menuName,
icon: getIconComponent(menuItem.icon || 'Document'), // 使用菜单项的图标,如果没有则使用默认图标
iconColor: getIconColorByMenuType(menuItem.menuType) || '#67C23A', // 使用菜单类型的颜色,如果没有则使用默认颜色
icon: menuItem.icon || 'document', // 使用数据库中的图标类名
iconColor: menuItem.iconColor || getIconColorByMenuType(menuItem.menuType) || '#67C23A', // 优先使用数据库中的颜色,否则使用菜单类型的颜色
route: route
};
}).filter(item => item.route); // 过滤掉 route 为空的项
@@ -436,8 +436,8 @@ const convertMenuIdsToQuickAccess = async (menuIds) => {
return {
key: matchedMenu.perms || matchedMenu.path || `menu_${matchedMenu.menuId}`,
label: matchedMenu.menuName,
icon: getIconComponent(matchedMenu.icon),
iconColor: getIconColorByMenuType(matchedMenu.menuType),
icon: matchedMenu.icon || 'document', // 使用数据库中的图标类名
iconColor: matchedMenu.iconColor || getIconColorByMenuType(matchedMenu.menuType),
route: fullPath || matchedMenu.path // 确保 route 不为空
};
} catch (error) {
@@ -446,8 +446,8 @@ const convertMenuIdsToQuickAccess = async (menuIds) => {
return {
key: matchedMenu.perms || matchedMenu.path || `menu_${matchedMenu.menuId}`,
label: matchedMenu.menuName,
icon: getIconComponent(matchedMenu.icon),
iconColor: getIconColorByMenuType(matchedMenu.menuType),
icon: matchedMenu.icon || 'document', // 使用数据库中的图标类名
iconColor: matchedMenu.iconColor || getIconColorByMenuType(matchedMenu.menuType),
route: matchedMenu.path || matchedMenu.fullPath || '/' // 确保 route 不为空
};
}
@@ -490,81 +490,6 @@ const flattenMenuTree = (menuTree) => {
return result;
};
// 获取图标组件
const getIconComponent = (iconName) => {
if (!iconName) return Document;
// 移除前缀,如 fa-, el-icon-
const cleanIconName = iconName.replace(/^(fa-|el-icon-)/, '').toLowerCase();
const iconMap = {
'menu': markRaw(Menu),
'grid': markRaw(Grid),
'folder': markRaw(Folder),
'tickets': markRaw(Tickets),
'document': markRaw(Document),
'setting': markRaw(Setting),
'user': markRaw(User),
'goods': markRaw(Goods),
'chat-dot-square': markRaw(ChatDotSquare),
'histogram': markRaw(Histogram),
'wallet': markRaw(Wallet),
'office-building': markRaw(OfficeBuilding),
'postcard': markRaw(Postcard),
'collection': markRaw(Collection),
'video-play': markRaw(VideoPlay),
'camera': markRaw(Camera),
'headset': markRaw(Headset),
'phone': markRaw(Phone),
'message': markRaw(Message),
'chat-line-square': markRaw(ChatLineSquare),
'chat-round': markRaw(ChatRound),
'guide': markRaw(Guide),
'help': markRaw(Help),
'info-filled': markRaw(InfoFilled),
'circle-check': markRaw(CircleCheck),
'circle-close': markRaw(CircleClose),
'warning': markRaw(Warning),
'question-filled': markRaw(QuestionFilled),
'star': markRaw(Star),
'link': markRaw(Link),
'position': markRaw(Position),
'picture': markRaw(Picture),
'upload': markRaw(Upload),
'download': markRaw(Download),
'caret-left': markRaw(CaretLeft),
'caret-right': markRaw(CaretRight),
'more': markRaw(More),
'close': markRaw(Close),
'check': markRaw(Check),
'arrow-up': markRaw(ArrowUp),
'arrow-down': markRaw(ArrowDown),
'arrow-left': markRaw(ArrowLeft),
'arrow-right': markRaw(ArrowRight),
'plus': markRaw(Plus),
'minus': markRaw(Minus),
'zoom-in': markRaw(ZoomIn),
'zoom-out': markRaw(ZoomOut),
'refresh': markRaw(Refresh),
'search': markRaw(Search),
'edit': markRaw(Edit),
'delete': markRaw(Delete),
'share': markRaw(Share),
'view': markRaw(View),
'switch-button': markRaw(SwitchButton),
'hide': markRaw(Hide),
'finished': markRaw(Finished),
'circle-plus': markRaw(CirclePlus),
'remove': markRaw(Remove),
'circle-check-filled': markRaw(CircleCheckFilled),
'circle-close-filled': markRaw(CircleCloseFilled),
'warning-filled': markRaw(WarningFilled),
'info-filled-icon': markRaw(InfoFilledIcon),
'success-filled': markRaw(SuccessFilled),
'question-filled-icon': markRaw(QuestionFilledIcon)
};
return iconMap[cleanIconName] || markRaw(Document);
};
// 根据菜单类型获取图标颜色
const getIconColorByMenuType = (menuType) => {
@@ -581,48 +506,48 @@ const getDefaultQuickAccessConfig = () => {
switch (role) {
case 'doctor':
return [
{ key: 'outpatient', label: '门诊接诊', icon: markRaw(ChatDotRound), iconColor: '#409eff', route: '/doctorstation' },
{ key: 'emr', label: '病历管理', icon: markRaw(Document), iconColor: '#67c23a', route: '/doctorstation/doctorphrase' },
{ key: 'prescription', label: '开立处方', icon: markRaw(Box), iconColor: '#e6a23c', route: '/clinicmanagement/ePrescribing' },
{ key: 'history', label: '历史处方', icon: markRaw(Clock), iconColor: '#f56c6c', route: '/clinicmanagement/historicalPrescription' },
{ key: 'schedule', label: '排班管理', icon: markRaw(Calendar), iconColor: '#909399', route: '/appoinmentmanage/deptManage' },
{ key: 'inquiry', label: '患者查询', icon: markRaw(Search), iconColor: '#409eff', route: '/patientmanagement' }
{ key: 'outpatient', label: '门诊接诊', icon: 'chat-dot-round', iconColor: '#409eff', route: '/doctorstation' },
{ key: 'emr', label: '病历管理', icon: 'document', iconColor: '#67c23a', route: '/doctorstation/doctorphrase' },
{ key: 'prescription', label: '开立处方', icon: 'box', iconColor: '#e6a23c', route: '/clinicmanagement/ePrescribing' },
{ key: 'history', label: '历史处方', icon: 'clock', iconColor: '#f56c6c', route: '/clinicmanagement/historicalPrescription' },
{ key: 'schedule', label: '排班管理', icon: 'calendar', iconColor: '#909399', route: '/appoinmentmanage/deptManage' },
{ key: 'inquiry', label: '患者查询', icon: 'search', iconColor: '#409eff', route: '/patientmanagement' }
];
case 'nurse':
return [
{ key: 'ward', label: '病房管理', icon: markRaw(User), iconColor: '#409eff', route: '/inpatientNurse/inpatientNurseStation' },
{ key: 'execution', label: '医嘱执行', icon: markRaw(Operation), iconColor: '#67c23a', route: '/inpatientNurse/medicalOrderExecution' },
{ key: 'proofread', label: '医嘱核对', icon: markRaw(Document), iconColor: '#e6a23c', route: '/inpatientNurse/medicalOrderProofread' },
{ key: 'drugCollect', label: '领药管理', icon: markRaw(Box), iconColor: '#f56c6c', route: '/inpatientNurse/medicineCollect' },
{ key: 'tpr', label: '体温单', icon: markRaw(Monitor), iconColor: '#909399', route: '/inpatientNurse/tprsheet' },
{ key: 'nursing', label: '护理记录', icon: markRaw(ChatDotRound), iconColor: '#409eff', route: '/inpatientNurse/nursingRecord' }
{ key: 'ward', label: '病房管理', icon: 'user', iconColor: '#409eff', route: '/inpatientNurse/inpatientNurseStation' },
{ key: 'execution', label: '医嘱执行', icon: 'operation', iconColor: '#67c23a', route: '/inpatientNurse/medicalOrderExecution' },
{ key: 'proofread', label: '医嘱核对', icon: 'document', iconColor: '#e6a23c', route: '/inpatientNurse/medicalOrderProofread' },
{ key: 'drugCollect', label: '领药管理', icon: 'box', iconColor: '#f56c6c', route: '/inpatientNurse/medicineCollect' },
{ key: 'tpr', label: '体温单', icon: 'monitor', iconColor: '#909399', route: '/inpatientNurse/tprsheet' },
{ key: 'nursing', label: '护理记录', icon: 'chat-dot-round', iconColor: '#409eff', route: '/inpatientNurse/nursingRecord' }
];
case 'pharmacist':
return [
{ key: 'dispensing', label: '发药管理', icon: markRaw(Box), iconColor: '#409eff', route: '/pharmacymanagement' },
{ key: 'prescription', label: '处方审核', icon: markRaw(Document), iconColor: '#67c23a', route: '/pharmacymanagement' },
{ key: 'inventory', label: '库存管理', icon: markRaw(Van), iconColor: '#e6a23c', route: '/medicineStorage' },
{ key: 'purchase', label: '采购管理', icon: markRaw(ShoppingCart), iconColor: '#f56c6c', route: '/medicineStorage' },
{ key: 'warning', label: '效期预警', icon: markRaw(Warning), iconColor: '#f56c6c', route: '/medicationmanagement/statisticalManagement/statisticalManagement' },
{ key: 'statistics', label: '用药统计', icon: markRaw(DataLine), iconColor: '#909399', route: '/monitor' }
{ key: 'dispensing', label: '发药管理', icon: 'box', iconColor: '#409eff', route: '/pharmacymanagement' },
{ key: 'prescription', label: '处方审核', icon: 'document', iconColor: '#67c23a', route: '/pharmacymanagement' },
{ key: 'inventory', label: '库存管理', icon: 'van', iconColor: '#e6a23c', route: '/medicineStorage' },
{ key: 'purchase', label: '采购管理', icon: 'shopping-cart', iconColor: '#f56c6c', route: '/medicineStorage' },
{ key: 'warning', label: '效期预警', icon: 'warning', iconColor: '#f56c6c', route: '/medicationmanagement/statisticalManagement/statisticalManagement' },
{ key: 'statistics', label: '用药统计', icon: 'data-line', iconColor: '#909399', route: '/monitor' }
];
case 'cashier':
return [
{ key: 'registration', label: '挂号收费', icon: markRaw(Money), iconColor: '#409eff', route: '/charge/outpatientregistration' },
{ key: 'clinicCharge', label: '门诊收费', icon: markRaw(Wallet), iconColor: '#67c23a', route: '/charge/cliniccharge' },
{ key: 'refund', label: '退费管理', icon: markRaw(Document), iconColor: '#e6a23c', route: '/charge/clinicrefund' },
{ key: 'invoice', label: '发票打印', icon: markRaw(Files), iconColor: '#f56c6c', route: '/basicmanage/InvoiceManagement' },
{ key: 'record', label: '收费记录', icon: markRaw(Clock), iconColor: '#909399', route: '/charge/clinicRecord' },
{ key: 'insurance', label: '医保结算', icon: markRaw(Bell), iconColor: '#409eff', route: '/ybmanagement' }
{ key: 'registration', label: '挂号收费', icon: 'money', iconColor: '#409eff', route: '/charge/outpatientregistration' },
{ key: 'clinicCharge', label: '门诊收费', icon: 'wallet', iconColor: '#67c23a', route: '/charge/cliniccharge' },
{ key: 'refund', label: '退费管理', icon: 'document', iconColor: '#e6a23c', route: '/charge/clinicrefund' },
{ key: 'invoice', label: '发票打印', icon: 'files', iconColor: '#f56c6c', route: '/basicmanage/InvoiceManagement' },
{ key: 'record', label: '收费记录', icon: 'clock', iconColor: '#909399', route: '/charge/clinicRecord' },
{ key: 'insurance', label: '医保结算', icon: 'bell', iconColor: '#409eff', route: '/ybmanagement' }
];
default: // admin
return [
{ key: 'patient', label: '患者管理', icon: markRaw(User), iconColor: '#409eff', route: '/patient/patientmgr' },
{ key: 'appointment', label: '预约管理', icon: markRaw(Calendar), iconColor: '#67c23a', route: '/appoinmentmanage' },
{ key: 'doctor', label: '医生管理', icon: markRaw(User), iconColor: '#e6a23c', route: '/doctorstation' },
{ key: 'surgery', label: '手术管理', icon: markRaw(Operation), iconColor: '#f56c6c', route: '/surgerymanage' },
{ key: 'drug', label: '药品管理', icon: markRaw(Box), iconColor: '#909399', route: '/pharmacymanagement' },
{ key: 'statistic', label: '数据统计', icon: markRaw(TrendCharts), iconColor: '#409eff', route: '/monitor' }
{ key: 'patient', label: '患者管理', icon: 'user', iconColor: '#409eff', route: '/patient/patientmgr' },
{ key: 'appointment', label: '预约管理', icon: 'calendar', iconColor: '#67c23a', route: '/appoinmentmanage' },
{ key: 'doctor', label: '医生管理', icon: 'user', iconColor: '#e6a23c', route: '/doctorstation' },
{ key: 'surgery', label: '手术管理', icon: 'operation', iconColor: '#f56c6c', route: '/surgerymanage' },
{ key: 'drug', label: '药品管理', icon: 'box', iconColor: '#909399', route: '/pharmacymanagement' },
{ key: 'statistic', label: '数据统计', icon: 'trend-charts', iconColor: '#409eff', route: '/monitor' }
];
}
};
@@ -639,12 +564,7 @@ const updatePendingEmrTodo = () => {
}
// 今日日程
const scheduleList = ref([
{ id: 1, time: '09:00', title: '科室晨会', location: '第一会议室', type: 'info', tag: '日常' },
{ id: 2, time: '10:30', title: '病例讨论', location: '第二会议室', type: 'primary', tag: '会议' },
{ id: 3, time: '14:00', title: '专家查房', location: '住院部3楼', type: 'warning', tag: '重要' },
{ id: 4, time: '16:00', title: '新药培训', location: '培训中心', type: 'success', tag: '培训' }
])
const scheduleList = ref([])
// 获取问候语
const getGreeting = () => {
@@ -980,8 +900,58 @@ const getTaskIcon = (category) => {
// 获取日程数据实际应用中应该从API获取
const fetchScheduleList = async () => {
// TODO: 调用API获取真实数据
console.log('Fetching schedule list...')
try {
console.log('Fetching schedule list...')
const response = await getTodayDoctorScheduleList()
if (response.code === 200) {
// 将API返回的数据转换为前端所需的格式
const scheduleData = response.data.map((schedule, index) => {
// 根据排班类型设置标签类型
let tagType = 'info'
if (schedule.weekday) {
tagType = schedule.weekday.toLowerCase()
} else if (schedule.timePeriod) {
tagType = schedule.timePeriod.toLowerCase()
}
// 确定标题
const title = schedule.doctor ? `${schedule.doctor}医生排班` : '医生排班'
// 确定位置
const location = schedule.clinic || schedule.deptId || '未知科室'
return {
id: schedule.id || index,
time: schedule.startTime || '未知时间',
title: title,
location: location,
type: tagType,
tag: schedule.timePeriod || '排班'
}
})
// 更新日程列表
scheduleList.value = scheduleData
} else {
console.error('获取排班信息失败:', response.msg)
// 如果API调用失败使用默认数据
scheduleList.value = [
{ id: 1, time: '09:00', title: '科室晨会', location: '第一会议室', type: 'info', tag: '日常' },
{ id: 2, time: '10:30', title: '病例讨论', location: '第二会议室', type: 'primary', tag: '会议' },
{ id: 3, time: '14:00', title: '专家查房', location: '住院部3楼', type: 'warning', tag: '重要' },
{ id: 4, time: '16:00', title: '新药培训', location: '培训中心', type: 'success', tag: '培训' }
]
}
} catch (error) {
console.error('获取排班信息异常:', error)
// 如果出现异常,使用默认数据
scheduleList.value = [
{ id: 1, time: '09:00', title: '科室晨会', location: '第一会议室', type: 'info', tag: '日常' },
{ id: 2, time: '10:30', title: '病例讨论', location: '第二会议室', type: 'primary', tag: '会议' },
{ id: 3, time: '14:00', title: '专家查房', location: '住院部3楼', type: 'warning', tag: '重要' },
{ id: 4, time: '16:00', title: '新药培训', location: '培训中心', type: 'success', tag: '培训' }
]
}
}
// 监听本地存储变化,以便在其他标签页或窗口中修改配置后更新当前页面