HomePage.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823
  1. <template>
  2. <div class="home-page">
  3. <!-- Top Bar -->
  4. <nav class="navbar">
  5. <div class="container-fluid">
  6. <div class="navbar-header">
  7. <img style="width: 60px; height: 48.5px; float: left; margin: 10px 5px 0px 0px;" src="/api/resource/Logo" />
  8. <a class="navbar-brand" style="font-size:30px;line-height:35px;padding-left:20px;">{{ systemTitle }}</a>
  9. </div>
  10. <div class="collapse navbar-collapse" id="navbar-collapse" v-if="!isPublicPage">
  11. <div class="navbar-actions">
  12. <div class="user-info">
  13. <span class="user-name">{{ user.name }}</span>
  14. <span class="role-name">{{ user.roleName }}</span>
  15. </div>
  16. <button class="action-btn" @click="updatePwd">
  17. <i class="el-icon-lock"></i> 修改密码
  18. </button>
  19. <button class="action-btn" @click="swithSystem">
  20. <i class="el-icon-refresh"></i> 切换系统
  21. </button>
  22. <button class="action-btn" @click="logout">
  23. <i class="el-icon-switch-button"></i> 退出登录
  24. </button>
  25. </div>
  26. </div>
  27. </div>
  28. </nav>
  29. <!-- #Top Bar -->
  30. <div class="page_content">
  31. <!-- Sidebar -->
  32. <div class="leftsidebar" v-if="!isPublicPage">
  33. <div class="sidebar">
  34. <div class="user-info">
  35. <div class="user-details">
  36. <span class="user-name">{{ user.name }}</span>
  37. <span class="role-name">{{ user.roleName }}</span>
  38. </div>
  39. </div>
  40. <div class="menu-list">
  41. <!-- 递归渲染菜单 -->
  42. <template v-for="menu in panels" :key="menu.id">
  43. <div class="menu-item">
  44. <div
  45. class="menu-header"
  46. @click="menutoggle(menu)"
  47. >
  48. <i :class="menu.icon || 'el-icon-folder'" v-if="menu.cNodes.length > 0"></i>
  49. <i :class="menu.icon || 'el-icon-document'" v-else></i>
  50. <span>{{ menu.title }}</span>
  51. <i
  52. class="el-icon-arrow-right"
  53. :class="{ 'rotate': menu.childShow }"
  54. ></i>
  55. </div>
  56. <div
  57. class="sub-menu"
  58. v-if="menu.cNodes.length > 0 && menu.childShow"
  59. >
  60. <!-- 递归渲染子菜单 -->
  61. <template v-for="subMenu in menu.cNodes" :key="subMenu.id">
  62. <div
  63. v-if="subMenu.cNodes.length === 0"
  64. class="sub-menu-item"
  65. @click="navigateTo(subMenu)"
  66. >
  67. <i :class="subMenu.icon || 'el-icon-document'"></i>
  68. <span>{{ subMenu.title }}</span>
  69. </div>
  70. <!-- 处理三级及以上菜单 -->
  71. <div v-else class="sub-menu-parent">
  72. <div
  73. class="sub-menu-header"
  74. @click="menutoggle(subMenu)"
  75. >
  76. <i :class="subMenu.icon || 'el-icon-folder'"></i>
  77. <span>{{ subMenu.title }}</span>
  78. <i
  79. class="el-icon-arrow-right"
  80. :class="{ 'rotate': subMenu.childShow }"
  81. ></i>
  82. </div>
  83. <div
  84. class="sub-sub-menu"
  85. v-if="subMenu.cNodes.length > 0 && subMenu.childShow"
  86. >
  87. <div
  88. v-for="subSubMenu in subMenu.cNodes"
  89. :key="subSubMenu.id"
  90. class="sub-sub-menu-item"
  91. @click="navigateTo(subSubMenu)"
  92. >
  93. <i :class="subSubMenu.icon || 'el-icon-document'"></i>
  94. <span>{{ subSubMenu.title }}</span>
  95. </div>
  96. </div>
  97. </div>
  98. </template>
  99. </div>
  100. </div>
  101. </template>
  102. </div>
  103. </div>
  104. </div>
  105. <!-- #Sidebar -->
  106. <!-- Main Content -->
  107. <section class="content">
  108. <div class="container-fluid">
  109. <router-view></router-view>
  110. </div>
  111. </section>
  112. <!-- #Main Content -->
  113. </div>
  114. </div>
  115. </template>
  116. <script>
  117. export default {
  118. name: 'HomePage',
  119. data() {
  120. return {
  121. systemTitle: '联达拖轮管理系统',
  122. user: {
  123. name: '',
  124. roleName: ''
  125. },
  126. isPublicPage: false,
  127. menuList: [],
  128. panels: []
  129. }
  130. },
  131. mounted() {
  132. // 从本地存储获取用户信息
  133. this.user.name = localStorage.getItem('username') || ''
  134. this.user.roleName = localStorage.getItem('roleName') || ''
  135. this.isPublicPage = localStorage.getItem('publicPage') === 'true'
  136. // 加载菜单列表
  137. console.log('isPublicPage:', this.isPublicPage)
  138. console.log('开始获取菜单列表')
  139. this.getMenuList()
  140. },
  141. methods: {
  142. // 获取菜单列表
  143. async getMenuList() {
  144. try {
  145. console.log('开始执行getMenuList方法')
  146. const token = localStorage.getItem('token')
  147. console.log('获取到的token:', token)
  148. if (!token) {
  149. this.$message.error('未获取到登录令牌')
  150. return
  151. }
  152. // 从后端API获取菜单数据
  153. console.log('准备发送请求到后端获取菜单')
  154. const response = await fetch('/api/menu/getMenus', {
  155. method: 'GET',
  156. headers: {
  157. 'Authorization': `Bearer ${token}`,
  158. 'Content-Type': 'application/json'
  159. }
  160. })
  161. console.log('获取菜单的响应状态:', response.status)
  162. console.log('获取菜单的响应状态文本:', response.statusText)
  163. if (!response.ok) {
  164. throw new Error('获取菜单失败,状态码: ' + response.status)
  165. }
  166. const menuData = await response.json()
  167. console.log('从后端获取到的菜单数据:', menuData)
  168. // 转换后端返回的菜单数据格式为前端需要的格式
  169. const formattedMenuList = this.convertMenuFormat(menuData)
  170. console.log('转换后的菜单数据:', formattedMenuList)
  171. this.menuList = formattedMenuList
  172. if (this.menuList.length > 0) {
  173. this.panels = this.buildMenuTree(this.menuList, 'mainMenu')
  174. this.initActive(this.panels)
  175. console.log('构建完成的菜单面板:', this.panels)
  176. }
  177. } catch (error) {
  178. console.error('获取菜单失败:', error)
  179. this.$message.error('获取菜单失败,请刷新页面重试')
  180. // 使用默认菜单作为 fallback
  181. this.useDefaultMenus()
  182. }
  183. },
  184. // 转换后端返回的菜单数据格式
  185. convertMenuFormat(menuData) {
  186. console.log('转换前的菜单数据:', menuData)
  187. return menuData.map(menu => {
  188. const formattedMenu = {
  189. MenuId: menu.id,
  190. Title: menu.title,
  191. Icon: menu.icon || 'el-icon-document',
  192. Url: menu.url || '',
  193. IsVisible: 1,
  194. Childrens: []
  195. }
  196. if (menu.subMenus && menu.subMenus.length > 0) {
  197. formattedMenu.Childrens = this.convertMenuFormat(menu.subMenus)
  198. }
  199. return formattedMenu
  200. })
  201. },
  202. // 使用默认菜单作为 fallback
  203. useDefaultMenus() {
  204. const defaultMenus = [
  205. {
  206. MenuId: 1,
  207. Title: '首页',
  208. Icon: 'el-icon-s-home',
  209. Url: 'mainindex',
  210. IsVisible: 1,
  211. Childrens: []
  212. },
  213. {
  214. MenuId: 2,
  215. Title: '通知管理',
  216. Icon: 'el-icon-bell',
  217. Url: '',
  218. IsVisible: 1,
  219. Childrens: [
  220. {
  221. MenuId: 21,
  222. Title: '我的通知',
  223. Icon: 'el-icon-message',
  224. Url: 'mynotice',
  225. IsVisible: 1,
  226. Childrens: []
  227. },
  228. {
  229. MenuId: 22,
  230. Title: '公告管理',
  231. Icon: 'el-icon-document',
  232. Url: 'announcement',
  233. IsVisible: 1,
  234. Childrens: []
  235. }
  236. ]
  237. },
  238. {
  239. MenuId: 3,
  240. Title: '调度管理',
  241. Icon: 'el-icon-s-operation',
  242. Url: '',
  243. IsVisible: 1,
  244. Childrens: [
  245. {
  246. MenuId: 31,
  247. Title: '引航计划',
  248. Icon: 'el-icon-s-grid',
  249. Url: 'pilot-plan',
  250. IsVisible: 1,
  251. Childrens: []
  252. }
  253. ]
  254. },
  255. {
  256. MenuId: 4,
  257. Title: '系统设置',
  258. Icon: 'el-icon-setting',
  259. Url: '',
  260. IsVisible: 1,
  261. Childrens: [
  262. {
  263. MenuId: 41,
  264. Title: '角色管理',
  265. Icon: 'el-icon-user',
  266. Url: 'role',
  267. IsVisible: 1,
  268. Childrens: []
  269. },
  270. {
  271. MenuId: 42,
  272. Title: '用户管理',
  273. Icon: 'el-icon-users',
  274. Url: 'user',
  275. IsVisible: 1,
  276. Childrens: []
  277. }
  278. ]
  279. }
  280. ]
  281. this.menuList = defaultMenus
  282. this.panels = this.buildMenuTree(this.menuList, 'mainMenu')
  283. this.initActive(this.panels)
  284. },
  285. // 构建菜单树
  286. buildMenuTree(menuList, parentId) {
  287. const returnVal = []
  288. menuList.forEach(menu => {
  289. if (menu.IsVisible !== 1) {
  290. return
  291. }
  292. const pmenu = {
  293. id: menu.MenuId,
  294. title: menu.Title,
  295. icon: menu.Icon,
  296. name: menu.Url,
  297. url: menu.Url,
  298. cNodes: [],
  299. childShow: 0,
  300. parentIds: parentId
  301. }
  302. // 递归构建子菜单,支持多级菜单
  303. if (menu.Childrens && menu.Childrens.length > 0) {
  304. pmenu.cNodes = this.buildMenuTree(menu.Childrens, parentId + '.' + pmenu.id)
  305. }
  306. returnVal.push(pmenu)
  307. })
  308. return returnVal
  309. },
  310. // 初始化菜单激活状态
  311. initActive(menuList) {
  312. const statename = this.$route.name
  313. menuList.forEach(value => {
  314. const p_realState = this.sliceStateName(value.name)
  315. if (p_realState === statename) {
  316. this.menutoggle(value)
  317. }
  318. this.initActive(value.cNodes)
  319. })
  320. },
  321. // 点击菜单事件
  322. menutoggle(node) {
  323. const childShow = node.cNodes.length > 0 ? (node.childShow > 0 ? 0 : 1) : 1
  324. const codes = node.parentIds.split('.')
  325. codes.push(node.id)
  326. this.resetActive(this.panels, codes)
  327. node.childShow = childShow
  328. },
  329. // 重置菜单激活状态
  330. resetActive(list, codes) {
  331. list.forEach(node => {
  332. const index = codes.indexOf(node.id)
  333. node.childShow = index >= 0 ? 1 : 0
  334. if (node.cNodes && node.cNodes.length > 0) {
  335. this.resetActive(node.cNodes, codes)
  336. }
  337. })
  338. },
  339. // 导航到菜单对应的路由
  340. navigateTo(menu) {
  341. if (menu.url) {
  342. this.$router.push(`/${menu.url}`)
  343. }
  344. },
  345. // 截取状态名称
  346. sliceStateName(statename) {
  347. if (!statename) return null
  348. const index = statename.indexOf('(')
  349. if (index >= 0) {
  350. return statename.slice(0, index)
  351. }
  352. return statename
  353. },
  354. // 修改密码
  355. updatePwd() {
  356. // 打开修改密码对话框
  357. this.$alert('修改密码功能开发中', '提示', {
  358. confirmButtonText: '确定'
  359. })
  360. },
  361. // 切换系统
  362. swithSystem() {
  363. // 模拟切换系统功能
  364. this.$confirm('是否切换系统?', '提示', {
  365. confirmButtonText: '确定',
  366. cancelButtonText: '取消',
  367. type: 'warning'
  368. }).then(() => {
  369. // 实际项目中需要调用API获取公司列表并切换
  370. this.$message.success('系统切换功能开发中')
  371. }).catch(() => {
  372. // 取消切换
  373. })
  374. },
  375. // 退出登录
  376. logout() {
  377. this.$confirm('是否退出登录?', '提示', {
  378. confirmButtonText: '确定',
  379. cancelButtonText: '取消',
  380. type: 'warning'
  381. }).then(() => {
  382. // 清除用户信息
  383. localStorage.removeItem('token')
  384. localStorage.removeItem('username')
  385. localStorage.removeItem('roleName')
  386. localStorage.removeItem('selectedCompany')
  387. localStorage.removeItem('publicPage')
  388. // 跳转到登录页面
  389. this.$router.push('/login')
  390. }).catch(() => {
  391. // 取消退出
  392. })
  393. }
  394. }
  395. }
  396. </script>
  397. <style scoped>
  398. /* 全局样式重置 */
  399. * {
  400. margin: 0;
  401. padding: 0;
  402. box-sizing: border-box;
  403. }
  404. /* 页面主容器 */
  405. .home-page {
  406. min-height: 100vh;
  407. background-color: #f5f5f5;
  408. }
  409. /* 顶部导航栏样式 */
  410. .navbar {
  411. background-color: #3c8dbc;
  412. color: white;
  413. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  414. position: fixed;
  415. top: 0;
  416. left: 0;
  417. right: 0;
  418. z-index: 1000;
  419. height: 64px;
  420. }
  421. .container-fluid {
  422. width: 100%;
  423. height: 100%;
  424. display: flex;
  425. justify-content: space-between;
  426. align-items: center;
  427. padding: 0 20px;
  428. }
  429. .navbar-header {
  430. display: flex;
  431. align-items: center;
  432. }
  433. .navbar-brand {
  434. display: flex;
  435. align-items: center;
  436. text-decoration: none;
  437. color: white;
  438. font-size: 20px;
  439. font-weight: 600;
  440. }
  441. .navbar-brand img {
  442. width: 48.5px;
  443. height: 48.5px;
  444. margin-right: 12px;
  445. }
  446. .collapse {
  447. display: flex;
  448. align-items: center;
  449. }
  450. .navbar-actions {
  451. display: flex;
  452. align-items: center;
  453. gap: 16px;
  454. }
  455. .user-info {
  456. display: flex;
  457. align-items: center;
  458. gap: 8px;
  459. }
  460. .user-name {
  461. font-size: 14px;
  462. font-weight: 500;
  463. }
  464. .role-name {
  465. font-size: 12px;
  466. opacity: 0.8;
  467. }
  468. .action-btn {
  469. background: none;
  470. border: none;
  471. color: white;
  472. cursor: pointer;
  473. font-size: 14px;
  474. padding: 8px 12px;
  475. border-radius: 4px;
  476. transition: background-color 0.2s ease;
  477. display: flex;
  478. align-items: center;
  479. gap: 4px;
  480. }
  481. .action-btn:hover {
  482. background-color: rgba(255, 255, 255, 0.1);
  483. }
  484. .action-btn i {
  485. font-size: 16px;
  486. }
  487. /* 页面布局 */
  488. .page_content {
  489. display: flex;
  490. justify-content: space-between;
  491. margin-top: 64px;
  492. min-height: calc(100vh - 64px);
  493. }
  494. /* 侧边栏样式 */
  495. .leftsidebar {
  496. width: 220px; /* 使用固定宽度,确保足够的空间显示菜单项 */
  497. min-width: 200px; /* 设置最小宽度,防止过度压缩 */
  498. max-width: 300px; /* 设置最大宽度,防止占用过多空间 */
  499. background-color: #222d32;
  500. color: white;
  501. position: fixed;
  502. top: 64px;
  503. left: 0;
  504. bottom: 0;
  505. overflow-y: auto;
  506. overflow-x: hidden; /* 隐藏水平滚动条 */
  507. transition: all 0.3s ease;
  508. z-index: 999;
  509. }
  510. .sidebar {
  511. height: 100%;
  512. display: flex;
  513. flex-direction: column;
  514. }
  515. /* 用户信息区域 */
  516. .sidebar .user-info {
  517. padding: 20px;
  518. border-bottom: 1px solid #367fa9;
  519. display: flex;
  520. flex-direction: column;
  521. align-items: center;
  522. gap: 8px;
  523. }
  524. .sidebar .user-details {
  525. text-align: center;
  526. }
  527. .sidebar .user-name {
  528. font-size: 16px;
  529. font-weight: 600;
  530. }
  531. .sidebar .role-name {
  532. font-size: 12px;
  533. opacity: 0.8;
  534. }
  535. /* 菜单列表样式 */
  536. .menu-list {
  537. flex: 1;
  538. padding: 16px 0;
  539. }
  540. .menu-item {
  541. margin-bottom: 4px;
  542. }
  543. .menu-header {
  544. display: flex;
  545. align-items: center;
  546. padding: 12px 20px;
  547. cursor: pointer;
  548. transition: background-color 0.3s ease;
  549. gap: 12px;
  550. }
  551. .menu-header:hover {
  552. background-color: #1e282c;
  553. }
  554. .menu-header i {
  555. font-size: 16px;
  556. width: 20px;
  557. text-align: center;
  558. }
  559. .menu-header span {
  560. flex: 1;
  561. font-size: 14px;
  562. }
  563. .menu-header .el-icon-arrow-right {
  564. transition: transform 0.3s ease;
  565. }
  566. .menu-header .el-icon-arrow-right.rotate {
  567. transform: rotate(90deg);
  568. }
  569. /* 子菜单样式 */
  570. .sub-menu {
  571. background-color: #1e282c;
  572. border-left: 3px solid #3c8dbc;
  573. }
  574. .sub-menu-item {
  575. display: flex;
  576. align-items: center;
  577. padding: 10px 20px 10px 48px;
  578. cursor: pointer;
  579. transition: background-color 0.3s ease;
  580. gap: 12px;
  581. }
  582. .sub-menu-item:hover {
  583. background-color: #1a2226;
  584. }
  585. .sub-menu-item i {
  586. font-size: 14px;
  587. width: 16px;
  588. text-align: center;
  589. }
  590. .sub-menu-item span {
  591. font-size: 13px;
  592. }
  593. /* 子菜单父项样式 */
  594. .sub-menu-parent {
  595. background-color: #1e282c;
  596. }
  597. .sub-menu-header {
  598. display: flex;
  599. align-items: center;
  600. padding: 10px 20px 10px 48px;
  601. cursor: pointer;
  602. transition: background-color 0.3s ease;
  603. gap: 12px;
  604. }
  605. .sub-menu-header:hover {
  606. background-color: #1a2226;
  607. }
  608. .sub-menu-header i {
  609. font-size: 14px;
  610. width: 16px;
  611. text-align: center;
  612. }
  613. .sub-menu-header span {
  614. font-size: 13px;
  615. flex: 1;
  616. }
  617. .sub-menu-header .el-icon-arrow-right {
  618. transition: transform 0.3s ease;
  619. font-size: 12px;
  620. }
  621. .sub-menu-header .el-icon-arrow-right.rotate {
  622. transform: rotate(90deg);
  623. }
  624. /* 三级及以上子菜单样式 */
  625. .sub-sub-menu {
  626. background-color: #1a2226;
  627. border-left: 3px solid #367fa9;
  628. }
  629. .sub-sub-menu-item {
  630. display: flex;
  631. align-items: center;
  632. padding: 8px 20px 8px 64px;
  633. cursor: pointer;
  634. transition: background-color 0.3s ease;
  635. gap: 12px;
  636. }
  637. .sub-sub-menu-item:hover {
  638. background-color: #161e21;
  639. }
  640. .sub-sub-menu-item i {
  641. font-size: 12px;
  642. width: 14px;
  643. text-align: center;
  644. }
  645. .sub-sub-menu-item span {
  646. font-size: 12px;
  647. }
  648. /* 主内容区域样式 */
  649. .content {
  650. margin-left: 220px; /* 与侧边栏宽度保持一致 */
  651. flex: 1;
  652. min-height: calc(100vh - 64px);
  653. padding: 20px;
  654. background-color: #ecf0f5;
  655. transition: all 0.3s ease;
  656. }
  657. .container-fluid {
  658. padding: 0;
  659. }
  660. /* 响应式设计 */
  661. @media (max-width: 1200px) {
  662. .leftsidebar {
  663. width: 200px; /* 在中等屏幕上适当减小侧边栏宽度 */
  664. }
  665. .content {
  666. margin-left: 200px; /* 与侧边栏宽度保持一致 */
  667. }
  668. }
  669. @media (max-width: 768px) {
  670. .container-fluid {
  671. padding: 0 16px;
  672. }
  673. .navbar-brand {
  674. font-size: 16px;
  675. }
  676. .navbar-brand img {
  677. width: 32px;
  678. height: 32px;
  679. }
  680. .navbar-actions {
  681. gap: 8px;
  682. }
  683. .action-btn {
  684. font-size: 12px;
  685. padding: 6px 8px;
  686. }
  687. .user-info {
  688. display: none;
  689. }
  690. .leftsidebar {
  691. width: 180px; /* 在小屏幕上进一步减小侧边栏宽度 */
  692. }
  693. .content {
  694. margin-left: 180px; /* 与侧边栏宽度保持一致 */
  695. }
  696. .menu-header,
  697. .sub-menu-item {
  698. padding-left: 12px;
  699. padding-right: 12px;
  700. }
  701. .sub-menu-item {
  702. padding-left: 36px;
  703. }
  704. .content {
  705. padding: 16px;
  706. }
  707. }
  708. /* 侧边栏折叠样式 */
  709. .sidebar-collapsed .leftsidebar {
  710. width: 60px; /* 适当增加折叠后的宽度,确保图标能正常显示 */
  711. }
  712. .sidebar-collapsed .content {
  713. margin-left: 60px; /* 与侧边栏宽度保持一致 */
  714. }
  715. .sidebar-collapsed .menu-header span,
  716. .sidebar-collapsed .sub-menu-item span,
  717. .sidebar-collapsed .sidebar .user-info {
  718. display: none;
  719. }
  720. .sidebar-collapsed .menu-header,
  721. .sidebar-collapsed .sub-menu-item {
  722. padding-left: 20px;
  723. padding-right: 20px;
  724. justify-content: center;
  725. }
  726. .sidebar-collapsed .sub-menu {
  727. display: none;
  728. }
  729. </style>