AppShell.vue 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. <template>
  2. <div class="app-shell">
  3. <ModuleRail :active-module="activeModule" @select="onModuleSelect" />
  4. <ContextNav
  5. :title="contextTitle"
  6. :items="contextItems"
  7. :collapsed="contextCollapsed"
  8. @toggle="contextCollapsed = !contextCollapsed"
  9. />
  10. <div class="app-shell__main">
  11. <SystemStatusBar
  12. :period-name="periodName"
  13. :phase="phase"
  14. :unread-count="unreadCount"
  15. />
  16. <main class="app-shell__content">
  17. <router-view />
  18. </main>
  19. </div>
  20. </div>
  21. </template>
  22. <script setup>
  23. import { ref, computed, watch, onMounted } from 'vue'
  24. import { useRoute, useRouter } from 'vue-router'
  25. import { useWorkspaceStore } from '@/stores/workspace'
  26. import api from '@/api'
  27. import ModuleRail from './ModuleRail.vue'
  28. import ContextNav from './ContextNav.vue'
  29. import SystemStatusBar from './SystemStatusBar.vue'
  30. import {
  31. HomeFilled, Aim, DataAnalysis, Setting,
  32. Monitor, List, Share, TrendCharts,
  33. DocumentChecked, ChatDotRound, Warning, Clock,
  34. Calendar, OfficeBuilding, Notebook, Grid, Tickets
  35. } from '@element-plus/icons-vue'
  36. const route = useRoute()
  37. const router = useRouter()
  38. const workspace = useWorkspaceStore()
  39. const unreadCount = ref(0)
  40. const activeModule = ref('workspace')
  41. const contextCollapsed = ref(false)
  42. const moduleContexts = {
  43. workspace: {
  44. title: '工作台',
  45. items: [
  46. { to: '/workspace', label: '当前周期', icon: Monitor }
  47. ]
  48. },
  49. okr: {
  50. title: '目标与对齐',
  51. items: [
  52. { to: '/okr/my', label: '我的 OKR', icon: Aim },
  53. { to: '/okr/team', label: '团队 OKR', icon: List },
  54. { to: '/okr/alignment', label: 'OKR 对齐', icon: Share },
  55. { to: '/okr/execution', label: '执行进度', icon: TrendCharts }
  56. ]
  57. },
  58. performance: {
  59. title: '绩效评审',
  60. items: [
  61. { to: '/scores/my', label: '我的绩效', icon: DocumentChecked },
  62. { to: '/scores/review', label: '待我评审', icon: DataAnalysis },
  63. { to: '/feedback', label: '绩效沟通', icon: ChatDotRound },
  64. { to: '/scores/appeal', label: '绩效申诉', icon: Warning },
  65. { to: '/scores/history', label: '历史绩效', icon: Clock }
  66. ]
  67. },
  68. admin: {
  69. title: '系统管理',
  70. items: [
  71. { to: '/periods', label: '考核周期', icon: Calendar },
  72. { to: '/org', label: '组织架构', icon: OfficeBuilding },
  73. { to: '/period/templates', label: '绩效模板', icon: Notebook },
  74. { to: '/dimensions', label: '考核维度', icon: Grid },
  75. { to: '/logs', label: '操作日志', icon: Tickets }
  76. ]
  77. }
  78. }
  79. const contextTitle = computed(() => moduleContexts[activeModule.value]?.title || '')
  80. const contextItems = computed(() => moduleContexts[activeModule.value]?.items || [])
  81. const periodName = computed(() => workspace.currentPeriod?.name || '')
  82. const phase = computed(() => workspace.currentPhase || '')
  83. function onModuleSelect(key) {
  84. activeModule.value = key
  85. const first = moduleContexts[key]?.items[0]
  86. if (first) router.push(first.to)
  87. }
  88. // Sync active module from route
  89. watch(() => route.path, (path) => {
  90. for (const [mod, ctx] of Object.entries(moduleContexts)) {
  91. if (ctx.items.some(i => path.startsWith(i.to))) {
  92. activeModule.value = mod
  93. return
  94. }
  95. }
  96. }, { immediate: true })
  97. onMounted(async () => {
  98. workspace.refreshOverview()
  99. try {
  100. const res = await api.get('/notifications/unread-count')
  101. unreadCount.value = res?.data?.count || res?.count || 0
  102. } catch { /* ignore */ }
  103. })
  104. </script>
  105. <style scoped>
  106. .app-shell {
  107. display: flex;
  108. min-height: 100vh;
  109. background: var(--workspace-bg, #f5f8fa);
  110. }
  111. .app-shell__main {
  112. flex: 1;
  113. min-width: 0;
  114. display: flex;
  115. flex-direction: column;
  116. }
  117. .app-shell__content {
  118. flex: 1;
  119. overflow-y: auto;
  120. padding: var(--spacing-lg, 16px);
  121. }
  122. </style>