Files
his/openhis-ui-vue3/src/views/features/index.vue
chenqi 98fe9f3301 feat(router): 添加医生工作站等功能模块路由配置
- 新增医生工作站路由,包含待写病历功能
- 添加全部功能模块路由,支持功能列表和配置页面
- 集成待办事项模块路由,完善工作流功能
- 配置相关API接口和服务类,实现用户配置管理
- 实现待写病历列表展示和相关业务逻辑
- 完善首页统计数据显示功能
2026-02-01 15:05:57 +08:00

386 lines
8.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>