feat(menu): 优化菜单服务性能并新增医生排班功能
- 添加菜单缓存注解以提升查询性能 - 实现菜单完整路径计算优化,解决 N+1 查询问题 - 新增 selectAllMenus 方法供路径计算使用 - 添加今日医生排班查询功能 - 重构前端图标显示逻辑,使用 SVG 图标替代 Element 图标 - 添加前端菜单数据本地缓存机制 - 更新菜单管理界面的表单组件绑定方式 - 新增预约管理、门诊管理和药房管理路由配置
This commit is contained in:
@@ -39,9 +39,7 @@
|
||||
<span class="custom-tree-node">
|
||||
<div class="tree-node-info">
|
||||
<div class="node-main">
|
||||
<el-icon :size="16" :color="getIconColor(data)">
|
||||
<component :is="getIconComponent(data.icon)" />
|
||||
</el-icon>
|
||||
<svg-icon :icon-class="data.icon" :style="{ color: getIconColor(data) }" />
|
||||
<span class="menu-label" style="margin-left: 8px;">{{ node.label }}</span>
|
||||
<el-tag v-if="data.fullPath" type="info" size="small" effect="plain" class="path-tag-inline">
|
||||
{{ data.fullPath }}
|
||||
@@ -88,9 +86,7 @@
|
||||
class="selected-function-item"
|
||||
>
|
||||
<div class="function-info">
|
||||
<el-icon :size="16" :color="getIconColor(item)">
|
||||
<component :is="getIconComponent(item.icon)" />
|
||||
</el-icon>
|
||||
<svg-icon :icon-class="item.icon" :style="{ color: getIconColor(item) }" />
|
||||
<div class="function-details">
|
||||
<span class="function-name">{{ item.menuName }}</span>
|
||||
<el-tag v-if="item.fullPath" type="info" size="small" class="function-path-below">
|
||||
@@ -193,6 +189,7 @@ import {
|
||||
SuccessFilled,
|
||||
QuestionFilled as QuestionFilledIcon
|
||||
} from '@element-plus/icons-vue'
|
||||
import SvgIcon from '@/components/SvgIcon'
|
||||
|
||||
// 添加 loading 状态
|
||||
const loading = ref(false)
|
||||
@@ -214,81 +211,6 @@ watch(filterText, (val) => {
|
||||
treeRef.value?.filter(val)
|
||||
})
|
||||
|
||||
// 图标映射
|
||||
const iconMap = {
|
||||
'menu': Menu,
|
||||
'grid': Grid,
|
||||
'folder': Folder,
|
||||
'tickets': Tickets,
|
||||
'document': Document,
|
||||
'setting': Setting,
|
||||
'user': User,
|
||||
'goods': Goods,
|
||||
'chat-dot-square': ChatDotSquare,
|
||||
'histogram': Histogram,
|
||||
'wallet': Wallet,
|
||||
'office-building': OfficeBuilding,
|
||||
'postcard': Postcard,
|
||||
'collection': Collection,
|
||||
'video-play': VideoPlay,
|
||||
'camera': Camera,
|
||||
'headset': Headset,
|
||||
'phone': Phone,
|
||||
'message': Message,
|
||||
'chat-line-square': ChatLineSquare,
|
||||
'chat-round': ChatRound,
|
||||
'guide': Guide,
|
||||
'help': Help,
|
||||
'info-filled': InfoFilled,
|
||||
'circle-check': CircleCheck,
|
||||
'circle-close': CircleClose,
|
||||
'warning': Warning,
|
||||
'question-filled': QuestionFilled,
|
||||
'star': Star,
|
||||
'link': Link,
|
||||
'position': Position,
|
||||
'picture': Picture,
|
||||
'upload': Upload,
|
||||
'download': Download,
|
||||
'caret-left': CaretLeft,
|
||||
'caret-right': CaretRight,
|
||||
'more': More,
|
||||
'close': Close,
|
||||
'check': Check,
|
||||
'arrow-up': ArrowUp,
|
||||
'arrow-down': ArrowDown,
|
||||
'arrow-left': ArrowLeft,
|
||||
'arrow-right': ArrowRight,
|
||||
'plus': Plus,
|
||||
'minus': Minus,
|
||||
'zoom-in': ZoomIn,
|
||||
'zoom-out': ZoomOut,
|
||||
'refresh': Refresh,
|
||||
'search': Search,
|
||||
'edit': Edit,
|
||||
'delete': Delete,
|
||||
'share': Share,
|
||||
'view': View,
|
||||
'switch-button': SwitchButton,
|
||||
'hide': Hide,
|
||||
'finished': Finished,
|
||||
'circle-plus': CirclePlus,
|
||||
'remove': Remove,
|
||||
'circle-check-filled': CircleCheckFilled,
|
||||
'circle-close-filled': CircleCloseFilled,
|
||||
'warning-filled': WarningFilled,
|
||||
'info-filled-icon': InfoFilledIcon,
|
||||
'success-filled': SuccessFilled,
|
||||
'question-filled-icon': QuestionFilledIcon
|
||||
}
|
||||
|
||||
// 获取图标组件
|
||||
const getIconComponent = (iconName) => {
|
||||
if (!iconName) return Document
|
||||
// 移除前缀,如 fa-, el-icon-
|
||||
const cleanIconName = iconName.replace(/^(fa-|el-icon-)/, '').toLowerCase()
|
||||
return iconMap[cleanIconName] || Document
|
||||
}
|
||||
|
||||
// 获取图标颜色
|
||||
const getIconColor = (data) => {
|
||||
@@ -302,6 +224,24 @@ const getIconColor = (data) => {
|
||||
const loadMenuData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// 尝试从本地缓存获取菜单数据
|
||||
const cachedMenuData = localStorage.getItem('menuTreeCache');
|
||||
const cacheTimestamp = localStorage.getItem('menuTreeCacheTimestamp');
|
||||
|
||||
// 检查缓存是否有效(24小时内)
|
||||
if (cachedMenuData && cacheTimestamp) {
|
||||
const cacheAge = Date.now() - parseInt(cacheTimestamp);
|
||||
if (cacheAge < 24 * 60 * 60 * 1000) { // 24小时
|
||||
menuTree.value = JSON.parse(cachedMenuData);
|
||||
// 展开所有节点
|
||||
expandedKeys.value = getAllNodeIds(menuTree.value);
|
||||
// 获取已保存的配置
|
||||
await loadSavedConfig();
|
||||
loading.value = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const response = await listMenu({})
|
||||
if (response.code === 200) {
|
||||
// 过滤掉隐藏的菜单项、目录和按钮类型的菜单,只保留当前角色可访问的菜单项
|
||||
@@ -311,6 +251,10 @@ const loadMenuData = async () => {
|
||||
// 展开所有节点
|
||||
expandedKeys.value = getAllNodeIds(filteredMenus)
|
||||
|
||||
// 将菜单数据缓存到本地存储
|
||||
localStorage.setItem('menuTreeCache', JSON.stringify(filteredMenus));
|
||||
localStorage.setItem('menuTreeCacheTimestamp', Date.now().toString());
|
||||
|
||||
// 获取已保存的配置
|
||||
await loadSavedConfig()
|
||||
} else {
|
||||
@@ -389,7 +333,7 @@ const saveConfig = async () => {
|
||||
fullPath: fullPath,
|
||||
menuName: node.menuName,
|
||||
path: node.path,
|
||||
icon: node.icon, // 保存图标信息
|
||||
icon: node.icon, // 保存数据库中的图标类名
|
||||
menuType: node.menuType // 保存菜单类型信息
|
||||
};
|
||||
|
||||
@@ -406,7 +350,7 @@ const saveConfig = async () => {
|
||||
fullPath: node.path,
|
||||
menuName: node.menuName,
|
||||
path: node.path,
|
||||
icon: node.icon, // 保存图标信息
|
||||
icon: node.icon, // 保存数据库中的图标类名
|
||||
menuType: node.menuType // 保存菜单类型信息
|
||||
};
|
||||
console.log(`构造的菜单项对象(错误处理):`, menuItem);
|
||||
@@ -435,6 +379,9 @@ const saveConfig = async () => {
|
||||
if (saveResult.code === 200) {
|
||||
// 只有在数据库保存成功后,才保存到本地存储
|
||||
localStorage.setItem('homeFeaturesConfig', JSON.stringify(menuDataWithPaths))
|
||||
// 清除菜单树缓存,以便下次加载最新数据
|
||||
localStorage.removeItem('menuTreeCache');
|
||||
localStorage.removeItem('menuTreeCacheTimestamp');
|
||||
ElMessage.success('配置保存成功')
|
||||
// 触发全局事件,通知首页更新快捷功能
|
||||
window.dispatchEvent(new Event('homeFeaturesConfigUpdated'));
|
||||
@@ -493,6 +440,36 @@ const filterNode = (value, data) => {
|
||||
// 加载已保存的配置
|
||||
const loadSavedConfig = async () => {
|
||||
try {
|
||||
// 尝试从本地缓存获取配置
|
||||
const cachedConfig = localStorage.getItem('homeFeaturesConfigCache');
|
||||
const cacheTimestamp = localStorage.getItem('homeFeaturesConfigCacheTimestamp');
|
||||
|
||||
// 检查缓存是否有效(1小时内)
|
||||
if (cachedConfig && cacheTimestamp) {
|
||||
const cacheAge = Date.now() - parseInt(cacheTimestamp);
|
||||
if (cacheAge < 60 * 60 * 1000) { // 1小时
|
||||
const parsedConfig = JSON.parse(cachedConfig);
|
||||
// 检查数据格式,如果是包含对象的数组(新格式),提取菜单ID
|
||||
if (parsedConfig && Array.isArray(parsedConfig)) {
|
||||
if (parsedConfig.length > 0 && typeof parsedConfig[0] === 'object' && parsedConfig[0].hasOwnProperty('menuId')) {
|
||||
// 新格式:[{menuId: 1, fullPath: "...", ...}, ...]
|
||||
checkedKeys.value = parsedConfig.map(item => item.menuId);
|
||||
} else {
|
||||
// 旧格式:[1, 2, 3, ...]
|
||||
checkedKeys.value = parsedConfig;
|
||||
}
|
||||
|
||||
// 根据保存的配置初始化已选择的功能
|
||||
const allNodes = getAllNodes(menuTree.value)
|
||||
const checkedNodes = allNodes.filter(node =>
|
||||
checkedKeys.value.includes(node.menuId) && node.menuType === 'C'
|
||||
)
|
||||
selectedFunctions.value = checkedNodes
|
||||
}
|
||||
return; // 使用缓存数据,直接返回
|
||||
}
|
||||
}
|
||||
|
||||
// 优先从数据库获取已保存的配置
|
||||
const response = await getCurrentUserConfig('homeFeaturesConfig')
|
||||
let savedConfig = null;
|
||||
@@ -539,6 +516,10 @@ const loadSavedConfig = async () => {
|
||||
checkedKeys.value.includes(node.menuId) && node.menuType === 'C'
|
||||
)
|
||||
selectedFunctions.value = checkedNodes
|
||||
|
||||
// 将配置缓存到本地存储
|
||||
localStorage.setItem('homeFeaturesConfigCache', JSON.stringify(parsedConfig));
|
||||
localStorage.setItem('homeFeaturesConfigCacheTimestamp', Date.now().toString());
|
||||
} else {
|
||||
// 如果解析失败,使用默认配置
|
||||
const defaultSelections = getDefaultSelections(menuTree.value)
|
||||
|
||||
Reference in New Issue
Block a user