MainLayout.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. <template>
  2. <el-container class="layout-container">
  3. <!-- 侧边栏 -->
  4. <el-aside :width="isCollapse ? '64px' : '240px'" class="layout-aside">
  5. <div class="logo-container">
  6. <img src="@/assets/logo.svg" alt="Logo" class="logo" />
  7. <transition name="fade">
  8. <span v-if="!isCollapse" class="logo-text">婚恋管理系统</span>
  9. </transition>
  10. </div>
  11. <el-menu
  12. :default-active="activeMenu"
  13. :collapse="isCollapse"
  14. :unique-opened="true"
  15. router
  16. class="aside-menu"
  17. background-color="transparent"
  18. text-color="#cbd5e1"
  19. active-text-color="#ffffff"
  20. >
  21. <!-- 数据面板 - 仅超级管理员 -->
  22. <el-menu-item v-if="isSuperAdmin" index="/dashboard">
  23. <el-icon><DataBoard /></el-icon>
  24. <template #title>数据面板</template>
  25. </el-menu-item>
  26. <el-menu-item index="/banner">
  27. <el-icon><Picture /></el-icon>
  28. <template #title>轮播图管理</template>
  29. </el-menu-item>
  30. <el-menu-item index="/announcement">
  31. <el-icon><Microphone /></el-icon>
  32. <template #title>小喇叭公告</template>
  33. </el-menu-item>
  34. <!-- 管理员管理 - 仅超级管理员 -->
  35. <el-menu-item v-if="isSuperAdmin" index="/admin-user">
  36. <el-icon><User /></el-icon>
  37. <template #title>管理员管理</template>
  38. </el-menu-item>
  39. <el-sub-menu index="/activity">
  40. <template #title>
  41. <el-icon><Calendar /></el-icon>
  42. <span>活动管理</span>
  43. </template>
  44. <el-menu-item index="/activity/list">活动列表</el-menu-item>
  45. <el-menu-item index="/activity/create">创建活动</el-menu-item>
  46. </el-sub-menu>
  47. <el-sub-menu index="/matchmaker">
  48. <template #title>
  49. <el-icon><User /></el-icon>
  50. <span>红娘管理</span>
  51. </template>
  52. <el-menu-item index="/matchmaker/list">红娘列表</el-menu-item>
  53. <el-menu-item index="/matchmaker/audit">红娘审核</el-menu-item>
  54. <el-menu-item index="/matchmaker/create">添加红娘</el-menu-item>
  55. <el-menu-item index="/matchmaker/points-product">积分商品</el-menu-item>
  56. <el-menu-item index="/matchmaker/case-audit">案例审核</el-menu-item>
  57. </el-sub-menu>
  58. <el-sub-menu index="/course">
  59. <template #title>
  60. <el-icon><Reading /></el-icon>
  61. <span>课程管理</span>
  62. </template>
  63. <el-menu-item index="/course/list">课程列表</el-menu-item>
  64. <el-menu-item index="/course/create">创建课程</el-menu-item>
  65. </el-sub-menu>
  66. <el-sub-menu index="/success-case">
  67. <template #title>
  68. <el-icon><TrophyBase /></el-icon>
  69. <span>成功案例</span>
  70. </template>
  71. <el-menu-item index="/success-case/list">案例列表</el-menu-item>
  72. <el-menu-item index="/success-case/create">创建案例</el-menu-item>
  73. </el-sub-menu>
  74. <el-sub-menu index="/user">
  75. <template #title>
  76. <el-icon><UserFilled /></el-icon>
  77. <span>用户管理</span>
  78. </template>
  79. <el-menu-item index="/user/list">用户列表</el-menu-item>
  80. <el-menu-item index="/user/vip">VIP用户</el-menu-item>
  81. </el-sub-menu>
  82. <!-- VIP套餐 - 仅超级管理员 -->
  83. <el-menu-item v-if="isSuperAdmin" index="/vip-package">
  84. <el-icon><Medal /></el-icon>
  85. <template #title>VIP套餐</template>
  86. </el-menu-item>
  87. <el-menu-item index="/dynamic">
  88. <el-icon><ChatDotSquare /></el-icon>
  89. <template #title>动态管理</template>
  90. </el-menu-item>
  91. <el-menu-item index="/report">
  92. <el-icon><Warning /></el-icon>
  93. <template #title>举报管理</template>
  94. </el-menu-item>
  95. </el-menu>
  96. </el-aside>
  97. <!-- 主内容区 -->
  98. <el-container>
  99. <!-- 顶部导航 -->
  100. <el-header class="layout-header">
  101. <div class="header-left">
  102. <el-icon class="collapse-icon" @click="toggleCollapse">
  103. <Fold v-if="!isCollapse" />
  104. <Expand v-else />
  105. </el-icon>
  106. <el-breadcrumb separator="/">
  107. <el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
  108. <el-breadcrumb-item v-if="currentRoute.meta.title">
  109. {{ currentRoute.meta.title }}
  110. </el-breadcrumb-item>
  111. </el-breadcrumb>
  112. </div>
  113. <div class="header-right">
  114. <span class="username">{{ userInfo?.username || '管理员' }}</span>
  115. <el-dropdown @command="handleCommand">
  116. <el-avatar :size="35" src="https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png" />
  117. <template #dropdown>
  118. <el-dropdown-menu>
  119. <el-dropdown-item command="profile">个人资料</el-dropdown-item>
  120. <el-dropdown-item command="setting">系统设置</el-dropdown-item>
  121. <el-dropdown-item divided command="logout">退出登录</el-dropdown-item>
  122. </el-dropdown-menu>
  123. </template>
  124. </el-dropdown>
  125. </div>
  126. </el-header>
  127. <!-- 主内容 -->
  128. <el-main class="layout-main">
  129. <router-view v-slot="{ Component }">
  130. <transition name="fade" mode="out-in">
  131. <component :is="Component" />
  132. </transition>
  133. </router-view>
  134. </el-main>
  135. </el-container>
  136. </el-container>
  137. </template>
  138. <script setup>
  139. import { ref, computed } from 'vue'
  140. import { useRoute, useRouter } from 'vue-router'
  141. import { useUserStore } from '@/stores/user'
  142. import { ElMessage, ElMessageBox } from 'element-plus'
  143. import {
  144. DataBoard,
  145. Picture,
  146. Microphone,
  147. Calendar,
  148. User,
  149. Reading,
  150. TrophyBase,
  151. UserFilled,
  152. ChatDotSquare,
  153. Warning,
  154. Fold,
  155. Expand,
  156. Medal
  157. } from '@element-plus/icons-vue'
  158. const route = useRoute()
  159. const router = useRouter()
  160. const userStore = useUserStore()
  161. const isCollapse = ref(false)
  162. const userInfo = computed(() => userStore.userInfo)
  163. const currentRoute = computed(() => route)
  164. const activeMenu = computed(() => route.path)
  165. const isSuperAdmin = computed(() => userStore.isSuperAdmin)
  166. // 切换侧边栏折叠状态
  167. const toggleCollapse = () => {
  168. isCollapse.value = !isCollapse.value
  169. }
  170. // 处理用户菜单命令
  171. const handleCommand = async (command) => {
  172. switch (command) {
  173. case 'profile':
  174. ElMessage.info('个人资料功能开发中')
  175. break
  176. case 'setting':
  177. ElMessage.info('系统设置功能开发中')
  178. break
  179. case 'logout':
  180. try {
  181. await ElMessageBox.confirm('确定要退出登录吗?', '提示', {
  182. confirmButtonText: '确定',
  183. cancelButtonText: '取消',
  184. type: 'warning'
  185. })
  186. await userStore.logout()
  187. ElMessage.success('退出成功')
  188. router.push('/login')
  189. } catch (error) {
  190. // 取消退出
  191. }
  192. break
  193. }
  194. }
  195. </script>
  196. <style scoped>
  197. /* ==================== 布局容器 ==================== */
  198. .layout-container {
  199. height: 100vh;
  200. overflow: hidden;
  201. }
  202. /* ==================== 侧边栏样式 ==================== */
  203. .layout-aside {
  204. background: linear-gradient(180deg, #1e293b 0%, #0f172a 100%);
  205. box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1);
  206. transition: width var(--transition-slow);
  207. position: relative;
  208. z-index: 100;
  209. }
  210. /* Logo 容器 */
  211. .logo-container {
  212. height: var(--header-height);
  213. display: flex;
  214. align-items: center;
  215. justify-content: center;
  216. padding: 0 var(--spacing-lg);
  217. border-bottom: 1px solid rgba(255, 255, 255, 0.08);
  218. background: rgba(255, 255, 255, 0.02);
  219. gap: var(--spacing-base);
  220. }
  221. .logo {
  222. width: 36px;
  223. height: 36px;
  224. flex-shrink: 0;
  225. filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1));
  226. transition: transform var(--transition-base);
  227. }
  228. .logo:hover {
  229. transform: scale(1.05);
  230. }
  231. .logo-text {
  232. font-size: var(--font-lg);
  233. font-weight: var(--font-bold);
  234. color: #ffffff;
  235. white-space: nowrap;
  236. letter-spacing: 0.5px;
  237. text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
  238. }
  239. /* 侧边栏菜单 */
  240. .aside-menu {
  241. border: none;
  242. height: calc(100vh - var(--header-height));
  243. overflow-y: auto;
  244. overflow-x: hidden;
  245. background-color: transparent;
  246. }
  247. /* 菜单项样式覆盖 */
  248. .aside-menu :deep(.el-menu-item) {
  249. margin: 4px 12px;
  250. border-radius: var(--radius-base);
  251. transition: all var(--transition-base);
  252. height: 48px;
  253. line-height: 48px;
  254. position: relative;
  255. overflow: hidden;
  256. }
  257. .aside-menu :deep(.el-menu-item:hover) {
  258. background-color: var(--sidebar-hover-bg) !important;
  259. color: #ffffff !important;
  260. }
  261. .aside-menu :deep(.el-menu-item.is-active) {
  262. background: linear-gradient(90deg, var(--primary-color) 0%, var(--primary-light) 100%) !important;
  263. color: #ffffff !important;
  264. font-weight: var(--font-semibold);
  265. box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
  266. }
  267. .aside-menu :deep(.el-menu-item.is-active::before) {
  268. content: '';
  269. position: absolute;
  270. left: 0;
  271. top: 50%;
  272. transform: translateY(-50%);
  273. width: 4px;
  274. height: 24px;
  275. background-color: #ffffff;
  276. border-radius: 0 4px 4px 0;
  277. opacity: 0.8;
  278. }
  279. /* 子菜单样式 */
  280. .aside-menu :deep(.el-sub-menu__title) {
  281. margin: 4px 12px;
  282. border-radius: var(--radius-base);
  283. transition: all var(--transition-base);
  284. height: 48px;
  285. line-height: 48px;
  286. }
  287. .aside-menu :deep(.el-sub-menu__title:hover) {
  288. background-color: var(--sidebar-hover-bg) !important;
  289. color: #ffffff !important;
  290. }
  291. .aside-menu :deep(.el-sub-menu.is-active > .el-sub-menu__title) {
  292. color: var(--primary-light) !important;
  293. font-weight: var(--font-medium);
  294. }
  295. .aside-menu :deep(.el-menu--inline) {
  296. background-color: rgba(0, 0, 0, 0.15);
  297. }
  298. .aside-menu :deep(.el-menu--inline .el-menu-item) {
  299. padding-left: 52px !important;
  300. margin: 2px 8px;
  301. height: 42px;
  302. line-height: 42px;
  303. }
  304. /* 菜单图标 */
  305. .aside-menu :deep(.el-icon) {
  306. font-size: 18px;
  307. margin-right: var(--spacing-base);
  308. transition: transform var(--transition-base);
  309. }
  310. .aside-menu :deep(.el-menu-item:hover .el-icon),
  311. .aside-menu :deep(.el-sub-menu__title:hover .el-icon) {
  312. transform: scale(1.1);
  313. }
  314. /* 折叠状态 */
  315. .aside-menu.el-menu--collapse :deep(.el-menu-item),
  316. .aside-menu.el-menu--collapse :deep(.el-sub-menu__title) {
  317. justify-content: center;
  318. }
  319. /* 滚动条美化 */
  320. .aside-menu::-webkit-scrollbar {
  321. width: 6px;
  322. }
  323. .aside-menu::-webkit-scrollbar-thumb {
  324. background-color: rgba(255, 255, 255, 0.2);
  325. border-radius: 3px;
  326. transition: background-color var(--transition-base);
  327. }
  328. .aside-menu::-webkit-scrollbar-thumb:hover {
  329. background-color: rgba(255, 255, 255, 0.3);
  330. }
  331. .aside-menu::-webkit-scrollbar-track {
  332. background-color: transparent;
  333. }
  334. /* ==================== 顶部导航样式 ==================== */
  335. .layout-header {
  336. display: flex;
  337. justify-content: space-between;
  338. align-items: center;
  339. background-color: var(--bg-primary);
  340. border-bottom: 1px solid var(--border-color);
  341. padding: 0 var(--spacing-xl);
  342. height: var(--header-height);
  343. box-shadow: var(--shadow-sm);
  344. position: relative;
  345. z-index: 90;
  346. }
  347. .header-left {
  348. display: flex;
  349. align-items: center;
  350. gap: var(--spacing-xl);
  351. }
  352. .collapse-icon {
  353. font-size: 20px;
  354. cursor: pointer;
  355. color: var(--text-secondary);
  356. transition: all var(--transition-base);
  357. padding: var(--spacing-sm);
  358. border-radius: var(--radius-base);
  359. }
  360. .collapse-icon:hover {
  361. color: var(--primary-color);
  362. background-color: var(--bg-hover);
  363. transform: scale(1.05);
  364. }
  365. .header-right {
  366. display: flex;
  367. align-items: center;
  368. gap: var(--spacing-lg);
  369. }
  370. .username {
  371. font-size: var(--font-sm);
  372. color: var(--text-secondary);
  373. font-weight: var(--font-medium);
  374. margin-right: var(--spacing-sm);
  375. }
  376. /* 头像样式 */
  377. .header-right :deep(.el-avatar) {
  378. cursor: pointer;
  379. border: 2px solid var(--border-color);
  380. transition: all var(--transition-base);
  381. }
  382. .header-right :deep(.el-avatar:hover) {
  383. border-color: var(--primary-color);
  384. transform: scale(1.05);
  385. box-shadow: 0 4px 12px rgba(59, 130, 246, 0.2);
  386. }
  387. /* 面包屑增强 */
  388. .header-left :deep(.el-breadcrumb) {
  389. font-size: var(--font-sm);
  390. }
  391. .header-left :deep(.el-breadcrumb__inner) {
  392. color: var(--text-secondary);
  393. transition: color var(--transition-base);
  394. }
  395. .header-left :deep(.el-breadcrumb__inner:hover) {
  396. color: var(--primary-color);
  397. }
  398. /* ==================== 主内容区样式 ==================== */
  399. .layout-main {
  400. background-color: var(--bg-secondary);
  401. padding: var(--spacing-xl);
  402. overflow-y: auto;
  403. height: calc(100vh - var(--header-height));
  404. position: relative;
  405. }
  406. /* ==================== 动画效果 ==================== */
  407. /* 路由过渡动画 */
  408. .fade-enter-active {
  409. animation: fadeInUp var(--transition-slow);
  410. }
  411. .fade-leave-active {
  412. animation: fadeOut var(--transition-fast);
  413. }
  414. @keyframes fadeOut {
  415. from {
  416. opacity: 1;
  417. }
  418. to {
  419. opacity: 0;
  420. }
  421. }
  422. /* Logo 文字淡入淡出 */
  423. .fade-enter-active,
  424. .fade-leave-active {
  425. transition: opacity var(--transition-base);
  426. }
  427. .fade-enter-from,
  428. .fade-leave-to {
  429. opacity: 0;
  430. }
  431. /* ==================== 响应式设计 ==================== */
  432. /* 平板设备 */
  433. @media (max-width: 1024px) {
  434. .layout-header {
  435. padding: 0 var(--spacing-lg);
  436. }
  437. .layout-main {
  438. padding: var(--spacing-lg);
  439. }
  440. .username {
  441. display: none;
  442. }
  443. }
  444. /* 移动设备 */
  445. @media (max-width: 768px) {
  446. .layout-aside {
  447. position: fixed;
  448. left: 0;
  449. top: 0;
  450. height: 100vh;
  451. z-index: 1000;
  452. }
  453. .layout-header {
  454. padding: 0 var(--spacing-md);
  455. }
  456. .header-left {
  457. gap: var(--spacing-md);
  458. }
  459. .layout-main {
  460. padding: var(--spacing-md);
  461. width: 100%;
  462. }
  463. .collapse-icon {
  464. font-size: 18px;
  465. }
  466. .header-right :deep(.el-avatar) {
  467. width: 32px;
  468. height: 32px;
  469. }
  470. }
  471. /* 超大屏幕优化 */
  472. @media (min-width: 1920px) {
  473. .layout-main {
  474. padding: var(--spacing-2xl) var(--spacing-3xl);
  475. }
  476. }
  477. </style>