- 新增医生工作站路由,包含待写病历功能 - 添加全部功能模块路由,支持功能列表和配置页面 - 集成待办事项模块路由,完善工作流功能 - 配置相关API接口和服务类,实现用户配置管理 - 实现待写病历列表展示和相关业务逻辑 - 完善首页统计数据显示功能
386 lines
8.6 KiB
Vue
386 lines
8.6 KiB
Vue
<template>
|
||
<div class="features-container">
|
||
<div class="page-header">
|
||
<h2>快捷功能</h2>
|
||
<p>这里展示了您配置的快捷功能模块</p>
|
||
</div>
|
||
|
||
<div v-loading="loading" element-loading-text="正在加载快捷功能...">
|
||
<div class="features-grid">
|
||
<div
|
||
v-for="feature in userFeatures"
|
||
:key="feature.menuId"
|
||
class="feature-card"
|
||
@click="goToFeature(feature.fullPath)"
|
||
>
|
||
<div class="feature-icon">
|
||
<el-icon :size="32" :color="getIconColor(feature)">
|
||
<component :is="getIconComponent(feature.icon)" />
|
||
</el-icon>
|
||
</div>
|
||
<div class="feature-title">{{ feature.menuName }}</div>
|
||
<div class="feature-path" v-if="feature.fullPath">{{ feature.fullPath }}</div>
|
||
<div class="feature-path" v-else-if="feature.path">{{ feature.path }}</div>
|
||
<div class="feature-desc">{{ feature.remark || '功能描述未设置' }}</div>
|
||
</div>
|
||
|
||
<div v-if="userFeatures.length === 0 && !loading" class="no-features">
|
||
暂无配置的快捷功能,请前往 <el-link type="primary" @click="goToConfig">功能配置</el-link> 页面进行设置
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, onMounted } from 'vue'
|
||
import { useRouter } from 'vue-router'
|
||
import { listMenu, getMenuFullPath } from '@/api/system/menu'
|
||
import { getCurrentUserConfig } from '@/api/system/userConfig'
|
||
import {
|
||
Menu,
|
||
Grid,
|
||
Folder,
|
||
Tickets,
|
||
Document,
|
||
Setting,
|
||
User,
|
||
Goods,
|
||
ChatDotSquare,
|
||
Histogram,
|
||
Wallet,
|
||
OfficeBuilding,
|
||
Postcard,
|
||
Collection,
|
||
VideoPlay,
|
||
Camera,
|
||
Headset,
|
||
Phone,
|
||
Message,
|
||
ChatLineSquare,
|
||
ChatRound,
|
||
Guide,
|
||
Help,
|
||
InfoFilled,
|
||
CircleCheck,
|
||
CircleClose,
|
||
Warning,
|
||
QuestionFilled,
|
||
Star,
|
||
Link,
|
||
Position,
|
||
Picture,
|
||
Upload,
|
||
Download,
|
||
CaretLeft,
|
||
CaretRight,
|
||
More,
|
||
Close,
|
||
Check,
|
||
ArrowUp,
|
||
ArrowDown,
|
||
ArrowLeft,
|
||
ArrowRight,
|
||
Plus,
|
||
Minus,
|
||
ZoomIn,
|
||
ZoomOut,
|
||
Refresh,
|
||
Search,
|
||
Edit,
|
||
Delete,
|
||
Share,
|
||
View,
|
||
SwitchButton,
|
||
Hide,
|
||
Finished,
|
||
CirclePlus,
|
||
Remove,
|
||
CircleCheckFilled,
|
||
CircleCloseFilled,
|
||
WarningFilled,
|
||
InfoFilled as InfoFilledIcon,
|
||
SuccessFilled,
|
||
QuestionFilled as QuestionFilledIcon
|
||
} from '@element-plus/icons-vue'
|
||
|
||
// 添加 loading 状态
|
||
const loading = ref(false)
|
||
|
||
const router = useRouter()
|
||
const userFeatures = ref([])
|
||
|
||
// 图标映射
|
||
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) => {
|
||
if (data.menuType === 'M') return '#409EFF' // 目录蓝色
|
||
if (data.menuType === 'C') return '#67C23A' // 菜单绿色
|
||
if (data.menuType === 'F') return '#E6A23C' // 按钮橙色
|
||
return '#909399' // 默认灰色
|
||
}
|
||
|
||
// 加载用户配置的快捷功能
|
||
const loadUserFeatures = async () => {
|
||
loading.value = true;
|
||
try {
|
||
// 获取用户配置的快捷功能数据
|
||
const configResponse = await getCurrentUserConfig('homeFeaturesConfig')
|
||
let menuDataWithPaths = [];
|
||
|
||
if (configResponse.code === 200 && configResponse.data) {
|
||
// 解析配置数据
|
||
try {
|
||
const decodedConfig = decodeURIComponent(configResponse.data);
|
||
menuDataWithPaths = JSON.parse(decodedConfig);
|
||
} catch (e) {
|
||
// 如果解码失败,尝试直接解析
|
||
try {
|
||
menuDataWithPaths = JSON.parse(configResponse.data);
|
||
} catch (e2) {
|
||
console.error('解析用户配置失败:', e2);
|
||
menuDataWithPaths = [];
|
||
}
|
||
}
|
||
} else {
|
||
// 如果没有配置,从本地存储获取
|
||
const localConfig = localStorage.getItem('homeFeaturesConfig');
|
||
if (localConfig) {
|
||
menuDataWithPaths = JSON.parse(localConfig);
|
||
}
|
||
}
|
||
|
||
// 直接使用保存的完整路径数据,无需再次调用API
|
||
userFeatures.value = menuDataWithPaths;
|
||
} catch (error) {
|
||
console.error('加载用户快捷功能失败:', error)
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
}
|
||
|
||
// 将树形菜单结构扁平化
|
||
const flattenMenuTree = (menuTree) => {
|
||
const result = [];
|
||
const traverse = (items) => {
|
||
for (const item of items) {
|
||
result.push(item);
|
||
if (item.children && item.children.length > 0) {
|
||
traverse(item.children);
|
||
}
|
||
}
|
||
};
|
||
traverse(menuTree);
|
||
return result;
|
||
}
|
||
|
||
// 跳转到功能页面
|
||
const goToFeature = (path) => {
|
||
if (path) {
|
||
// 检查是否为外部链接
|
||
if (path.startsWith('http://') || path.startsWith('https://')) {
|
||
// 如果是外部链接,使用 window.open 打开
|
||
window.open(path, '_blank');
|
||
} else {
|
||
// 确保内部路径以 / 开头,以保证正确的路由跳转
|
||
const normalizedPath = path.startsWith('/') ? path : '/' + path;
|
||
router.push(normalizedPath);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 跳转到配置页面
|
||
const goToConfig = () => {
|
||
router.push('/features/config')
|
||
}
|
||
|
||
onMounted(() => {
|
||
loadUserFeatures()
|
||
})
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.features-container {
|
||
padding: 20px;
|
||
background: #f5f7fa;
|
||
min-height: calc(100vh - 120px);
|
||
|
||
.page-header {
|
||
margin-bottom: 30px;
|
||
text-align: center;
|
||
|
||
h2 {
|
||
font-size: 24px;
|
||
color: #303133;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
p {
|
||
font-size: 14px;
|
||
color: #909399;
|
||
}
|
||
}
|
||
|
||
.features-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||
gap: 20px;
|
||
|
||
.feature-card {
|
||
background: white;
|
||
border-radius: 8px;
|
||
padding: 24px;
|
||
text-align: center;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||
border: 1px solid #ebeef5;
|
||
|
||
&:hover {
|
||
transform: translateY(-4px);
|
||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
|
||
border-color: #c6e2ff;
|
||
}
|
||
|
||
.feature-icon {
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.feature-title {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #303133;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.feature-path {
|
||
font-size: 12px;
|
||
color: #409EFF;
|
||
margin-bottom: 8px;
|
||
word-break: break-all;
|
||
padding: 2px 8px;
|
||
background-color: #ecf5ff;
|
||
border-radius: 4px;
|
||
display: inline-block;
|
||
}
|
||
|
||
.feature-desc {
|
||
font-size: 12px;
|
||
color: #909399;
|
||
line-height: 1.5;
|
||
}
|
||
}
|
||
|
||
.no-features {
|
||
grid-column: 1 / -1;
|
||
text-align: center;
|
||
padding: 40px;
|
||
color: #909399;
|
||
font-size: 16px;
|
||
|
||
.el-link {
|
||
font-size: inherit;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 响应式设计
|
||
@media (max-width: 768px) {
|
||
.features-grid {
|
||
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
||
gap: 12px;
|
||
|
||
.feature-card {
|
||
padding: 16px;
|
||
|
||
.feature-title {
|
||
font-size: 14px;
|
||
}
|
||
|
||
.feature-path {
|
||
font-size: 10px;
|
||
padding: 2px 4px;
|
||
}
|
||
|
||
.feature-desc {
|
||
font-size: 11px;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style> |