@@ -46,27 +46,31 @@
< div class = "quick-access-section" >
< div class = "section-header" >
< h3 > 快捷功能 < / h3 >
< el-button text type = "primary" @click ="showAllFunctions" > 查看全部 < / el -button >
< div >
< el-button text type = "primary" @click ="showConfig" > 配置 < / el -button >
< / div >
< / div >
< div class = "quick-a ccess-grid" >
< div
v-for = "func in quickAccess"
:key = "func.key "
class = "quick-access-card "
@click ="handleQ uickA ccess(func) "
>
< div class = "quick-icon" >
< el-icon :size = "28" :color = "func.iconColor ">
< component :is = "func.icon" / >
< / el-icon >
< div v-loading = "quickA ccessLoading" element-loading-text="加载快捷功能..." >
< div class = "quick-access-grid" >
< div
v-for = "func in quickAccess "
:key = "func.key "
class = "q uick-a ccess-card "
@click ="handleQuickAccess(func)"
>
< div class = "quick-icon ">
< el-icon :size = "28" :color = "func.iconColor" >
< component :is = "func.icon" / >
< / el-icon >
< / div >
< div class = "quick-label" > { { func . label } } < / div >
< / div >
< div class = "quick-label" > { { func . label } } < / div >
< / div >
< / div >
< / div >
<!-- 待办事项 -- >
< div class = "todo-section" v-if = "todoList.length > 0" >
< div class = "todo-section" >
< div class = "section-header" >
< h3 > 待办事项 < / h3 >
< el-badge :value = "todoList.length" class = "todo-badge" >
@@ -92,6 +96,9 @@
< / div >
< div class = "todo-time" > { { todo . time } } < / div >
< / div >
< div v-if = "todoList.length === 0" class="empty-todo" >
< el -empty description = "暂无待办事项" :image-size = "60" / >
< / div >
< / div >
< / div >
@@ -123,11 +130,15 @@
< / template >
< script setup name = "Home" >
import { ref , computed , onMounted } from 'vue'
import { ref , computed , onMounted , onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
import { markRaw } from 'vue'
import useUserStore from '@/store/modules/user'
import { getHomeStatistics } from '@/api/home'
import { getHomeStatistics , getPendingEmrCount } from '@/api/home'
import { listTodo } from '@/api/workflow/task'
import { getCurrentUserConfig } from '@/api/system/userConfig'
import { listMenu , getMenuFullPath } from '@/api/system/menu'
import { ElDivider } from 'element-plus'
import {
User ,
Document ,
@@ -149,9 +160,67 @@ import {
Van ,
Bell ,
Setting ,
Search
Search ,
Menu ,
Grid ,
Folder ,
Tickets ,
ChatDotSquare ,
Histogram ,
OfficeBuilding ,
Postcard ,
Collection ,
VideoPlay ,
Camera ,
Headset ,
Phone ,
Message ,
ChatLineSquare ,
ChatRound ,
Guide ,
Help ,
InfoFilled ,
CircleCheck ,
CircleClose ,
QuestionFilled ,
Star ,
Link ,
Position ,
Picture ,
Upload ,
Download ,
CaretLeft ,
CaretRight ,
More ,
Close ,
Check ,
ArrowUp ,
ArrowDown ,
ArrowLeft ,
ArrowRight ,
Plus ,
Minus ,
ZoomIn ,
ZoomOut ,
Refresh ,
Edit ,
Delete ,
Share ,
View ,
SwitchButton ,
Hide ,
Finished ,
CirclePlus ,
Remove ,
CircleCheckFilled ,
CircleCloseFilled ,
WarningFilled ,
Goods
} from '@element-plus/icons-vue'
// 为别名单独导入
import { InfoFilled as InfoFilledIcon , QuestionFilled as QuestionFilledIcon , SuccessFilled } from '@element-plus/icons-vue'
const userStore = useUserStore ( )
const router = useRouter ( )
@@ -180,7 +249,7 @@ const roleStatsConfig = {
doctor : [
{ key : 'myPatients' , label : '我的患者' , icon : markRaw ( User ) , type : 'primary' , iconColor : '#409eff' } ,
{ key : 'todayAppointments' , label : '今日门诊' , icon : markRaw ( Calendar ) , type : 'success' , iconColor : '#67c23a' } ,
{ key : 'pendingRecords ' , label : '待写病历' , icon : markRaw ( Document ) , type : 'warning' , iconColor : '#e6a23c' } ,
{ key : 'pendingEmr ' , label : '待写病历' , icon : markRaw ( Document ) , type : 'warning' , iconColor : '#e6a23c' } ,
{ key : 'prescriptions' , label : '今日处方' , icon : markRaw ( Box ) , type : 'info' , iconColor : '#909399' }
] ,
nurse : [
@@ -203,60 +272,371 @@ const roleStatsConfig = {
]
}
// 不同角色 的快捷功能配置
const role QuickAccessConfig = {
admin : [
{ key : 'patient' , label : '患者管理' , icon : markRaw ( User ) , iconColor : '#409eff' , route : '/patientmanagement' } ,
{ 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 ) , i conColor : '#409eff' , route : '/monitor' } ,
{ key : 'invoice' , label : '发票管理' , icon : markRaw ( Files ) , iconColor : '#67c23a' , route : '/basicmanage/InvoiceManagement' } ,
{ key : 'system' , label : '系统设置' , icon : markRaw ( Setting ) , iconColor : '#909399' , route : '/system' }
] ,
doctor : [
{ key : 'outpatient' , label : '门诊接诊' , icon : markRaw ( ChatDotRound ) , iconColor : '#409eff' , route : '/doctorstation' } ,
{ key : 'emr' , label : '病历管理' , i con: 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' }
] ,
nurse : [
{ 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 : '领药管理' , i con: markRaw ( Box ) , iconColor : '#f56c6c' , route : '/inpatientNurse/medicineCollect' } ,
{ key : 'tpr' , label : '体温单' , icon : markRaw ( Monitor ) , i conColor : '#909399' , route : '/inpatientNurse/tprsheet' } ,
{ key : 'nursing' , label : '护理记录' , icon : markRaw ( ChatDotRound ) , iconColor : '#409eff' , route : '/inpatientNurse/nursingRecord' }
] ,
ph armacist : [
{ 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' }
] ,
cashier : [
{ 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' }
]
}
// 从数据库获取用户配置 的快捷功能ID列表
const getUser QuickAccessConfig = async ( ) => {
try {
// 优先从数据库获取配置
const response = await getCurrentUserConfig ( 'homeFeaturesConfig' ) ;
console . log ( '从数据库获取的配置响应:' , response ) ;
// 检查响应结构,数据可能在 data 或 msg 字段中
let configData = null ;
if ( response . code === 200 ) {
if ( response . data ) {
configData = response . data ;
} else if ( response . msg ) {
// 如果数据在 msg 字段中( 可能是URL编码的)
configData = response . msg ;
}
}
if ( configData ) {
// 解码配置值( 如果是URL编码的)
let decodedData = configData ;
try {
decodedData = decodeURIComponent ( configData ) ;
} catch ( decodeError ) {
console . warn ( '解码配置数据失败,使用原始数据:' , decodeError ) ;
decodedData = configData ;
}
console . log ( '解码后的配置数据:' , decodedData ) ;
const parsedData = JSON . parse ( decodedData ) ;
console . log ( '解析后的配置数据:' , parsedData ) ;
return parsedData ;
}
} catch ( error ) {
console . error ( '从数据库获取用户配置失败:' , error ) ;
}
// 如果数据库中没有配置,尝试从本地存储获取
try {
const savedConfig = localStorage . getItem ( 'homeFeaturesConfig' ) ;
console . log ( '从本地存储获取的配置:' , savedConfig ) ;
if ( savedConfig ) {
const parsedData = JSON . parse ( savedConfig ) ;
console . log ( '从本地存储解析的配置数据:' , parsedData ) ;
return parsedData ;
}
} catch ( error ) {
console . error ( '从本地存储获取用户配置失败:' , error ) ;
}
// 如果没有配置,返回空数组
console . log ( '没有找到用户配置,返回空数组' ) ;
return [ ] ;
} ;
// 响应式数据存储快捷访问功能
const quickAccessData = ref ( [ ] ) ;
// 根据用户配置获取快捷功能
const quickAccess = computed ( ( ) => {
return quickAccessData . value ;
} ) ;
// 添加 loading 状态
const quickAccessLoading = ref ( false ) ;
// 异步加载用户配置
const loadUserQuickAccessConfig = async ( ) => {
quickAccessLoading . value = true ;
try {
console . log ( '开始加载用户快捷访问配置...' ) ;
const userConfig = await getUserQuickAccessConfig ( ) ;
console . log ( '获取到的用户配置:' , userConfig ) ;
// 如果用户没有配置任何功能,返回默认配置
if ( ! userConfig || userConfig . length === 0 ) {
console . log ( '用户没有配置任何功能,使用默认配置' ) ;
quickAccessData . value = getDefaultQuickAccessConfig ( ) ;
return ;
}
// 如果用户配置了功能ID列表, 需要从菜单中获取详细信息
console . log ( '开始转换菜单ID为快捷访问格式...' ) ;
const convertedFeatures = await convertMenuIdsToQuickAccess ( userConfig ) ;
console . log ( '转换后的功能:' , convertedFeatures ) ;
// 如果转换后没有功能( 可能是因为配置的ID没有对应的菜单项) , 返回默认配置
if ( ! convertedFeatures || convertedFeatures . length === 0 ) {
console . log ( '转换后没有功能,使用默认配置' ) ;
quickAccessData . value = getDefaultQuickAccessConfig ( ) ;
return ;
}
console . log ( '设置最终的快捷访问数据:' , convertedFeatures ) ;
quickAccessData . value = convertedFeatures ;
} catch ( error ) {
console . error ( '加载用户快捷访问配置失败:' , error ) ;
// 出错时使用默认配置
quickAccessData . value = getDefaultQuickAccessConfig ( ) ;
} finally {
quickAccessLoading . value = false ;
}
} ;
// 将菜单ID转换为快捷访问格式
const convertMenuIdsToQuickAccess = async ( menuIds ) => {
if ( ! menuIds || menuIds . length === 0 ) {
return [ ] ;
}
try {
// 检查 menuIds 是否已经是包含完整路径的对象数组(新格式)
if ( menuIds . length > 0 && typeof menuIds [ 0 ] === 'object' ) {
// 检查是否包含 fullPath 属性(新格式)或 menuId 属性(新格式但可能还没获取完整路径)
if ( menuIds [ 0 ] . hasOwnProperty ( 'fullPath' ) || menuIds [ 0 ] . hasOwnProperty ( 'menuId' ) ) {
// 如果是新格式,直接转换为快捷功能格式
return menuIds . map ( menuItem => {
// 如果没有 fullPath, 使用 path 作为备选
let route = menuItem . fullPath || menuItem . path ;
// 确保路径格式正确,去除多余的斜杠
if ( route && typeof route === 'string' ) {
// 将多个连续的斜杠替换为单个斜杠,但保留协议部分的双斜杠(如 http://)
route = route . replace ( /([^:])\/{2,}/g , '$1/' ) ;
}
return {
key : menuItem . menuId ,
label : menuItem . menuName ,
icon : getIconComponent ( menuItem . icon || 'Document' ) , // 使用菜单项的图标,如果没有则使用默认图标
iconColor : getIconColorByMenuType ( menuItem . menuType ) || '#67C23A' , // 使用菜单类型的颜色,如果没有则使用默认颜色
route : route
} ;
} ) . filter ( item => item . route ) ; // 过滤掉 route 为空的项
}
}
// 如果是旧格式( 仅包含菜单ID的数组) , 则按原方式处理
// 获取所有菜单数据
const response = await listMenu ( { } ) ;
if ( response . code === 200 ) {
const allMenus = response . data ;
// 扁平化菜单树结构,获取所有叶子节点(菜单项)
const flatMenus = flattenMenuTree ( allMenus ) ;
// 根据用户选择的菜单ID过滤并返回对应的功能对象
const menuPromises = menuIds . map ( async ( id ) => {
// 查找匹配的菜单项
const matchedMenu = flatMenus . find ( menu => menu . menuId == id ) ;
if ( matchedMenu ) {
// 获取完整路径
try {
const fullPathResponse = await getMenuFullPath ( matchedMenu . menuId ) ;
// 确保返回的路径不为空
const fullPath = ( fullPathResponse . code === 200 && fullPathResponse . data )
? fullPathResponse . data
: ( matchedMenu . path || matchedMenu . fullPath ) ;
// 将菜单数据转换为快捷功能格式
return {
key : matchedMenu . perms || matchedMenu . path || ` menu_ ${ matchedMenu . menuId } ` ,
label : matchedMenu . menuName ,
icon : getIconComponent ( matchedMenu . icon ) ,
iconColor : getIconColorByMenuType ( matchedMenu . menuType ) ,
route : fullPath || matchedMenu . path // 确保 route 不为空
} ;
} catch ( error ) {
console . error ( ` 获取菜单 ${ matchedMenu . menuName } 的完整路径失败: ` , error ) ;
// 如果获取完整路径失败,使用现有路径作为备选
return {
key : matchedMenu . perms || matchedMenu . path || ` menu_ ${ matchedMenu . menuId } ` ,
label : matchedMenu . menuName ,
icon : getIconComponent ( matchedMenu . icon ) ,
iconColor : getIconColorByMenuType ( matchedMenu . menuType ) ,
route : matchedMenu . path || matchedMenu . fullPath || '/' // 确保 route 不为空
} ;
}
}
return null ;
} ) ;
// 等待所有完整路径获取完成
const convertedMenus = await Promise . all ( menuPromises ) ;
// 过滤掉 route 为空的项和未找到的菜单ID
return convertedMenus . filter ( item => item !== null && item . route ) ;
}
} catch ( error ) {
console . error ( '获取菜单数据失败:' , error ) ;
}
// 如果获取失败,返回空数组
return [ ] ;
} ;
// 将菜单树结构扁平化
const flattenMenuTree = ( menuTree ) => {
const result = [ ] ;
const flatten = ( items ) => {
items . forEach ( item => {
// 只处理菜单类型为'C'(菜单)的项目,忽略目录('M')和按钮('F')
if ( item . menuType === 'C' ) {
result . push ( item ) ;
}
// 递归处理子菜单
if ( item . children && item . children . length > 0 ) {
flatten ( item . children ) ;
}
} ) ;
} ;
flatten ( 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 ) => {
if ( menuType === 'M' ) return '#409EFF' ; // 目录蓝色
if ( menuType === 'C' ) return '#67C23A' ; // 菜单绿色
if ( menuType === 'F' ) return '#E6A23C' ; // 按钮橙色
return '#909399' ; // 默认灰色
} ;
// 获取默认快捷功能配置
const getDefaultQuickAccessConfig = ( ) => {
// 根据不同角色返回默认配置
const role = userStore . roles [ 0 ] || 'admin' ;
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' }
] ;
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' }
] ;
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' }
] ;
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' }
] ;
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' }
] ;
}
} ;
// 待办事项
const todoList = ref ( [
{ id : 1 , title : '审核处方申请' , desc : '张医生提交的5条处方待审核' , priority : 'high' , icon : markRaw ( Document ) , time : '10分钟前' } ,
{ id : 2 , title : '确认患者入院' , desc : '李某某( 男, 45岁) 已办理入院' , priority : 'medium' , icon : markRaw ( User ) , time : '30分钟前' } ,
{ id : 3 , title : '处理投诉反馈' , desc : '患者家属对服务态度的投诉' , priority : 'high' , icon : markRaw ( ChatDotRound ) , time : '1小时前' } ,
{ id : 4 , title : '药品效期预警' , desc : '阿莫西林等3种药品即将过期' , priority : 'low' , icon : markRaw ( Warning ) , time : '2小时前' } ,
{ id : 5 , title : '月度报表审核' , desc : '11月份门诊收入报表待审核' , priority : 'medium' , icon : markRaw ( Files ) , time : '3小时前' }
] )
const todoList = ref ( [ ] )
// 更新待办事项中的待写病历数量
const updatePendingEmrTodo = ( ) => {
const pendingEmrTodo = todoList . value . find ( item => item . title === '待写病历' || item . desc ? . includes ( '患者等待写病历' ) ) ;
if ( pendingEmrTodo ) {
pendingEmrTodo . desc = ` 有 ${ statisticsData . value . pendingEmr || 0 } 个患者等待写病历 ` ;
}
}
// 今日日程
const scheduleList = ref ( [
@@ -310,95 +690,90 @@ const currentStats = computed(() => {
// 合并配置和实际数据
return baseConfig . map ( stat => {
const statWith = { ... stat }
// 根据不同的 key 获取对应的值
switch ( stat . key ) {
case 'totalPatients' :
statWith . value = statisticsData . value . totalPatients
statWith . trend = statisticsData . value . patientTrend
break
statWith . value = statisticsData . value . totalPatients ;
statWith . trend = statisticsData . value . patientTrend ;
break ;
case 'todayRevenue' :
statWith . value = statisticsData . value . todayRevenue
statWith . trend = statisticsData . value . revenueTrend
break
statWith . value = statisticsData . value . todayRevenue ;
statWith . trend = statisticsData . value . revenueTrend ;
break ;
case 'appointments' :
statWith . value = statisticsData . value . todayAppointments
statWith . trend = statisticsData . value . appointmentTrend
break
statWith . value = statisticsData . value . todayAppointments ;
statWith . trend = statisticsData . value . appointmentTrend ;
break ;
case 'pendingApprovals' :
statWith . value = statisticsData . value . pendingApprovals
break
statWith . value = statisticsData . value . pendingApprovals ;
break ;
case 'myPatients' :
statWith . value = statisticsData . value . totalPatients
statWith . trend = statisticsData . value . patientTrend
break
statWith . value = statisticsData . value . totalPatients ;
statWith . trend = statisticsData . value . patientTrend ;
break ;
case 'todayAppointments' :
statWith . value = statisticsData . value . todayAppointments
statWith . trend = statisticsData . value . appointmentTrend
break
case 'pendingRecords ' :
statWith . value = statisticsData . value . pendingApprovals
break
statWith . value = statisticsData . value . todayAppointments ;
statWith . trend = statisticsData . value . appointmentTrend ;
break ;
case 'pendingEmr ' :
statWith . value = statisticsData . value . pendingEmr ;
break ;
case 'prescriptions' :
statWith . value = statisticsData . value . todayAppointments
statWith . trend = statisticsData . value . appointmentTrend
break
statWith . value = statisticsData . value . todayAppointments ;
statWith . trend = statisticsData . value . appointmentTrend ;
break ;
case 'wardPatients' :
statWith . value = statisticsData . value . totalPatients
statWith . trend = statisticsData . value . patientTrend
break
statWith . value = statisticsData . value . totalPatients ;
statWith . trend = statisticsData . value . patientTrend ;
break ;
case 'todayTreatments' :
statWith . value = statisticsData . value . todayAppointments
statWith . trend = statisticsData . value . appointmentTrend
break
statWith . value = statisticsData . value . todayAppointments ;
statWith . trend = statisticsData . value . appointmentTrend ;
break ;
case 'vitalSigns' :
statWith . value = statisticsData . value . pendingApprovals
break
statWith . value = statisticsData . value . pendingApprovals ;
break ;
case 'drugDistribution' :
statWith . value = statisticsData . value . todayAppointments
statWith . trend = statisticsData . value . appointmentTrend
break
statWith . value = statisticsData . value . todayAppointments ;
statWith . trend = statisticsData . value . appointmentTrend ;
break ;
case 'todayPrescriptions' :
statWith . value = statisticsData . value . todayAppointments
statWith . trend = statisticsData . value . appointmentTrend
break
statWith . value = statisticsData . value . todayAppointments ;
statWith . trend = statisticsData . value . appointmentTrend ;
break ;
case 'pendingReview' :
statWith . value = statisticsData . value . pendingApprovals
break
statWith . value = statisticsData . value . pendingApprovals ;
break ;
case 'outOfStock' :
statWith . value = statisticsData . value . pendingApprovals
break
statWith . value = statisticsData . value . pendingApprovals ;
break ;
case 'nearExpiry' :
statWith . value = statisticsData . value . pendingApprovals
break
statWith . value = statisticsData . value . pendingApprovals ;
break ;
case 'todayPayments' :
statWith . value = statisticsData . value . todayRevenue
statWith . trend = statisticsData . value . revenueTrend
break
statWith . value = statisticsData . value . todayRevenue ;
statWith . trend = statisticsData . value . revenueTrend ;
break ;
case 'refundRequests' :
statWith . value = statisticsData . value . pendingApprovals
break
statWith . value = statisticsData . value . pendingApprovals ;
break ;
case 'pendingInvoices' :
statWith . value = statisticsData . value . pendingApprovals
break
statWith . value = statisticsData . value . pendingApprovals ;
break ;
case 'insuranceClaims' :
statWith . value = statisticsData . value . todayAppointments
statWith . trend = statisticsData . value . appointmentTrend
break
statWith . value = statisticsData . value . todayAppointments ;
statWith . trend = statisticsData . value . appointmentTrend ;
break ;
default :
statWith . value = '0'
statWith . trend = 0
statWith . value = '0' ;
statWith . trend = 0 ;
}
return statWith
} )
} )
// 根据角色获取快捷功能
const quickAccess = computed ( ( ) => {
const role = userStore . roles [ 0 ] || 'admin'
return roleQuickAccessConfig [ role ] || roleQuickAccessConfig . admin
} )
// 处理统计卡片点击
const handleStatClick = ( stat ) => {
@@ -419,13 +794,42 @@ const handleStatClick = (stat) => {
} else if ( stat . key === 'pendingApprovals' || stat . key === 'pendingReview' ) {
// 跳转到待审核页面
router . push ( '/clinicmanagement/ePrescribing' )
} else if ( stat . key === 'pendingEmr' ) {
// 跳转到待写病历页面
router . push ( '/doctorstation/pending-emr' )
}
}
// 处理快捷功能点击
const handleQuickAccess = ( func ) => {
if ( func . route ) {
router . push ( func . route )
// 检查是否为外部链接
if ( func . route . startsWith ( 'http://' ) || func . route . startsWith ( 'https://' ) ) {
// 如果是外部链接,使用 window.open 打开
window . open ( func . route , '_blank' ) ;
} else {
// 确保路径格式正确,去除多余的斜杠
let normalizedPath = func . route ;
if ( normalizedPath && typeof normalizedPath === 'string' ) {
// 将多个连续的斜杠替换为单个斜杠,但保留协议部分的双斜杠(如 http://)
normalizedPath = normalizedPath . replace ( /([^:])\/{2,}/g , '$1/' ) ;
}
// 确保内部路径以 / 开头,以保证正确的路由跳转
if ( ! normalizedPath . startsWith ( '/' ) ) {
normalizedPath = '/' + normalizedPath ;
}
try {
router . push ( normalizedPath ) ;
} catch ( error ) {
console . error ( '路由跳转失败:' , error ) ;
// 如果路径跳转失败,尝试使用原始路径
router . push ( func . route ) ;
}
}
} else {
console . warn ( '快捷功能没有配置路由路径:' , func ) ;
}
}
@@ -433,16 +837,22 @@ const handleQuickAccess = (func) => {
const handleTodoClick = ( todo ) => {
console . log ( 'Todo clicked:' , todo )
// 跳转到相应的处理页面
if ( todo . id === 6 ) { // 待写病历
router . push ( '/doctorstation?tab=pendingEmr' )
}
}
// 显示全部功能
const showAllFunctions = ( ) => {
// 跳转到功能菜单页面
// 显示功能配置
const showConfig = ( ) => {
// 跳转到功能配置页面
router . push ( '/features/config' )
}
// 显示全部待办
const showAllTodos = ( ) => {
// 跳转到待办事项页面
router . push ( '/todo' )
}
// 管理日程
@@ -461,12 +871,111 @@ const fetchStatsData = async () => {
} catch ( error ) {
console . error ( '获取统计数据失败:' , error )
}
try {
// 获取待写病历数量
const pendingEmrRes = await getPendingEmrCount ( ) ;
if ( pendingEmrRes . code === 200 ) {
// 确保统计数据对象中有pendingEmr字段
if ( ! statisticsData . value ) {
statisticsData . value = { }
}
statisticsData . value . pendingEmr = pendingEmrRes . data || 0 ;
} else {
statisticsData . value . pendingEmr = 0 ;
}
// 更新待办事项中的待写病历数量
updatePendingEmrTodo ( ) ;
} catch ( error ) {
console . error ( '获取待写病历数量失败:' , error )
// 确保统计数据对象中有pendingEmr字段
if ( ! statisticsData . value ) {
statisticsData . value = { }
}
statisticsData . value . pendingEmr = 0 ;
// 更新待办事项中的待写病历数量
updatePendingEmrTodo ( ) ;
}
}
// 获取待办事项( 实际应用中应该从API获取)
const fetchTodoList = async ( ) => {
// TODO: 调用API获取真实数据
console . log ( 'Fetching todo list...' )
try {
const response = await listTodo ( { pageNum : 1 , pageSize : 5 } )
if ( response . code === 200 ) {
// 将工作流任务数据转换为待办事项格式
const rows = response . rows || [ ] ;
const todoData = rows . slice ( 0 , 5 ) . map ( ( task , index ) => ( {
id : task . id || index ,
title : task . taskName || task . name || '待办事项' ,
desc : task . description || '暂无描述' ,
priority : getPriorityFromTask ( task ) ,
status : getTaskStatus ( task . status || task . state ) ,
icon : getTaskIcon ( task . category ) ,
time : parseTime ( task . createTime || task . createTimeStr , '{y}-{m}-{d} {h}:{i}' ) ,
taskInfo : task // 保存原始任务信息,便于后续处理
} ) )
// 检查是否已经有"待写病历"任务,如果没有则添加
const hasPendingEmrTask = todoData . some ( task => task . title === '待写病历' || task . desc ? . includes ( '患者等待写病历' ) ) ;
if ( ! hasPendingEmrTask && statisticsData . value . pendingEmr > 0 ) {
// 添加待写病历任务
const pendingEmrTask = {
id : Date . now ( ) , // 使用时间戳作为唯一ID
title : '待写病历' ,
desc : ` 有 ${ statisticsData . value . pendingEmr || 0 } 个患者等待写病历 ` ,
priority : 'high' ,
icon : markRaw ( Document ) ,
time : '刚刚' ,
taskInfo : null
} ;
// 如果数组未满5个, 添加到末尾; 否则替换最后一个
if ( todoData . length < 5 ) {
todoData . push ( pendingEmrTask ) ;
} else {
todoData [ 4 ] = pendingEmrTask ;
}
}
todoList . value = todoData ;
}
} catch ( error ) {
console . error ( '获取待办事项失败:' , error )
// 如果获取真实数据失败,保留空数组,但模块框架仍会显示
todoList . value = [ ] ;
}
}
// 根据任务信息确定优先级
const getPriorityFromTask = ( task ) => {
// 根据任务的某些属性来确定优先级,这里可以根据实际业务调整
if ( task . priority && task . priority > 50 ) return 'high'
if ( task . priority && task . priority > 20 ) return 'medium'
return 'low'
}
// 获取任务状态
const getTaskStatus = ( status ) => {
// 根据实际返回的状态值映射
if ( status === 'completed' || status === 'finish' ) return 'completed'
if ( status === 'processing' || status === 'active' ) return 'processing'
return 'pending'
}
// 获取任务图标
const getTaskIcon = ( category ) => {
// 根据任务分类确定图标
if ( category && category . includes ( 'approval' ) ) return markRaw ( Document )
if ( category && category . includes ( 'patient' ) ) return markRaw ( User )
if ( category && category . includes ( 'feedback' ) ) return markRaw ( ChatDotRound )
if ( category && category . includes ( 'warning' ) ) return markRaw ( Warning )
if ( category && category . includes ( 'report' ) ) return markRaw ( Files )
if ( category && category . includes ( 'data' ) ) return markRaw ( DataLine )
if ( category && category . includes ( 'operation' ) ) return markRaw ( Operation )
if ( category && category . includes ( 'system' ) ) return markRaw ( Setting )
return markRaw ( Document ) // 默认图标
}
// 获取日程数据( 实际应用中应该从API获取)
@@ -475,11 +984,37 @@ const fetchScheduleList = async () => {
console . log ( 'Fetching schedule list...' )
}
onMounted ( ( ) => {
// 监听本地存储变化,以便在其他标签页或窗口中修改配置后更新当前页面
const handleStorageChange = ( event ) => {
if ( event . key === 'homeFeaturesConfig' ) {
console . log ( '检测到快捷功能配置更新,正在重新加载...' ) ;
loadUserQuickAccessConfig ( ) ;
}
} ;
// 监听配置更新事件
const handleConfigUpdate = ( ) => {
console . log ( '检测到快捷功能配置更新事件,正在重新加载...' ) ;
loadUserQuickAccessConfig ( ) ;
} ;
onMounted ( async ( ) => {
fetchStatsData ( )
fetchTodoList ( )
await fetchTodoList( )
fetchScheduleList ( )
await loadUserQuickAccessConfig ( )
// 添加本地存储变化监听器
window . addEventListener ( 'storage' , handleStorageChange ) ;
// 添加配置更新事件监听器
window . addEventListener ( 'homeFeaturesConfigUpdated' , handleConfigUpdate ) ;
} )
// 在组件卸载前移除监听器
onUnmounted ( ( ) => {
window . removeEventListener ( 'storage' , handleStorageChange ) ;
window . removeEventListener ( 'homeFeaturesConfigUpdated' , handleConfigUpdate ) ;
} ) ;
< / script >
< style scoped lang = "scss" >
@@ -641,6 +1176,11 @@ onMounted(() => {
color : # 303133 ;
margin : 0 ;
}
. divider {
color : # d8d8d8 ;
margin : 0 8 px ;
}
}
. quick - access - grid {
@@ -702,6 +1242,11 @@ onMounted(() => {
color : # 303133 ;
margin : 0 ;
}
. divider {
color : # d8d8d8 ;
margin : 0 8 px ;
}
}
. todo - list {
@@ -762,6 +1307,11 @@ onMounted(() => {
color : # c0c4cc ;
}
}
. empty - todo {
padding : 20 px 0 ;
text - align : center ;
}
}
}
@@ -784,6 +1334,11 @@ onMounted(() => {
color : # 303133 ;
margin : 0 ;
}
. divider {
color : # d8d8d8 ;
margin : 0 8 px ;
}
}
. schedule - list {