AppShell.vue 3.5 KB

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