HomeTabs.vue 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. <template>
  2. <div class="home-tabs">
  3. <!-- 标签页头部 -->
  4. <div class="tabs-header">
  5. <div
  6. v-for="tab in tabs"
  7. :key="tab.id"
  8. class="tab-item"
  9. :class="{ 'active': activeTabId === tab.id }"
  10. @click="switchTab(tab.id)"
  11. >
  12. <span class="tab-title">{{ tab.title }}</span>
  13. <button
  14. class="tab-close"
  15. @click.stop="closeTab(tab.id)"
  16. :disabled="tab.id === 'mainindex'"
  17. >
  18. ×
  19. </button>
  20. </div>
  21. <div class="tab-add" @click="addTab">
  22. +
  23. </div>
  24. </div>
  25. <!-- 标签页内容 -->
  26. <div class="tabs-content">
  27. <router-view v-slot="{ Component }">
  28. <transition name="fade" mode="out-in">
  29. <component :is="Component" />
  30. </transition>
  31. </router-view>
  32. </div>
  33. </div>
  34. </template>
  35. <script>
  36. export default {
  37. name: 'HomeTabs',
  38. data() {
  39. return {
  40. // 标签页列表
  41. tabs: [
  42. {
  43. id: 'mainindex',
  44. title: '首页',
  45. path: '/mainindex'
  46. }
  47. ],
  48. // 当前激活的标签页ID
  49. activeTabId: 'mainindex'
  50. }
  51. },
  52. mounted() {
  53. // 初始化标签页
  54. this.initTabs()
  55. // 监听路由变化
  56. this.$router.beforeEach((to, from, next) => {
  57. this.handleRouteChange(to)
  58. next()
  59. })
  60. },
  61. methods: {
  62. // 初始化标签页
  63. initTabs() {
  64. // 从本地存储加载标签页状态
  65. const savedTabs = localStorage.getItem('homeTabs')
  66. const savedActiveTabId = localStorage.getItem('activeTabId')
  67. if (savedTabs) {
  68. this.tabs = JSON.parse(savedTabs)
  69. }
  70. if (savedActiveTabId) {
  71. this.activeTabId = savedActiveTabId
  72. }
  73. },
  74. // 保存标签页状态
  75. saveTabsState() {
  76. localStorage.setItem('homeTabs', JSON.stringify(this.tabs))
  77. localStorage.setItem('activeTabId', this.activeTabId)
  78. },
  79. // 处理路由变化
  80. handleRouteChange(to) {
  81. const path = to.path.replace('/', '')
  82. const tabId = path || 'mainindex'
  83. // 检查标签页是否已存在
  84. const existingTab = this.tabs.find(tab => tab.id === tabId)
  85. if (!existingTab) {
  86. // 添加新标签页
  87. this.addTabFromRoute(to)
  88. }
  89. // 激活当前标签页
  90. this.activeTabId = tabId
  91. this.saveTabsState()
  92. },
  93. // 从路由添加标签页
  94. addTabFromRoute(route) {
  95. const path = route.path.replace('/', '')
  96. const tabId = path || 'mainindex'
  97. // 根据路由路径生成标签页标题
  98. let title = '新页面'
  99. switch (path) {
  100. case 'mainindex':
  101. title = '首页'
  102. break
  103. case 'mynotice':
  104. title = '我的通知'
  105. break
  106. case 'announcement':
  107. title = '公告管理'
  108. break
  109. case 'role':
  110. title = '角色管理'
  111. break
  112. case 'user':
  113. title = '用户管理'
  114. break
  115. }
  116. // 添加新标签页
  117. this.tabs.push({
  118. id: tabId,
  119. title: title,
  120. path: route.path
  121. })
  122. this.saveTabsState()
  123. },
  124. // 切换标签页
  125. switchTab(tabId) {
  126. this.activeTabId = tabId
  127. // 查找标签页对应的路由路径
  128. const tab = this.tabs.find(t => t.id === tabId)
  129. if (tab) {
  130. this.$router.push(tab.path)
  131. }
  132. this.saveTabsState()
  133. },
  134. // 关闭标签页
  135. closeTab(tabId) {
  136. // 不允许关闭首页标签页
  137. if (tabId === 'mainindex') {
  138. return
  139. }
  140. const index = this.tabs.findIndex(tab => tab.id === tabId)
  141. if (index !== -1) {
  142. // 从标签页列表中移除
  143. this.tabs.splice(index, 1)
  144. // 如果关闭的是当前激活的标签页,切换到前一个标签页
  145. if (this.activeTabId === tabId) {
  146. const newActiveTab = this.tabs[this.tabs.length - 1]
  147. this.switchTab(newActiveTab.id)
  148. }
  149. this.saveTabsState()
  150. }
  151. },
  152. // 添加标签页
  153. addTab() {
  154. // 这里可以打开一个对话框,让用户选择要添加的页面
  155. // 暂时默认添加我的通知页面
  156. this.$router.push('/mynotice')
  157. }
  158. }
  159. }
  160. </script>
  161. <style scoped>
  162. .home-tabs {
  163. display: flex;
  164. flex-direction: column;
  165. height: 100%;
  166. }
  167. /* 标签页头部样式 */
  168. .tabs-header {
  169. display: flex;
  170. align-items: center;
  171. background-color: #f8fafc;
  172. border-bottom: 1px solid #e2e8f0;
  173. padding: 0 16px;
  174. height: 48px;
  175. overflow-x: auto;
  176. white-space: nowrap;
  177. }
  178. .tabs-header::-webkit-scrollbar {
  179. height: 4px;
  180. }
  181. .tabs-header::-webkit-scrollbar-track {
  182. background: #f1f5f9;
  183. }
  184. .tabs-header::-webkit-scrollbar-thumb {
  185. background: #cbd5e1;
  186. border-radius: 2px;
  187. }
  188. .tabs-header::-webkit-scrollbar-thumb:hover {
  189. background: #94a3b8;
  190. }
  191. /* 标签页项样式 */
  192. .tab-item {
  193. display: flex;
  194. align-items: center;
  195. padding: 0 16px;
  196. height: 36px;
  197. margin-right: 8px;
  198. background-color: #ffffff;
  199. border: 1px solid #e2e8f0;
  200. border-radius: 6px 6px 0 0;
  201. cursor: pointer;
  202. transition: all 0.3s ease;
  203. position: relative;
  204. top: 1px;
  205. }
  206. .tab-item:hover {
  207. background-color: #f0f9ff;
  208. border-color: #bae6fd;
  209. }
  210. .tab-item.active {
  211. background-color: #ffffff;
  212. border-bottom-color: #ffffff;
  213. box-shadow: 0 2px 0 #3b82f6;
  214. }
  215. .tab-title {
  216. font-size: 14px;
  217. color: #64748b;
  218. margin-right: 8px;
  219. }
  220. .tab-item.active .tab-title {
  221. color: #3b82f6;
  222. font-weight: 500;
  223. }
  224. /* 标签页关闭按钮 */
  225. .tab-close {
  226. background: none;
  227. border: none;
  228. font-size: 16px;
  229. color: #94a3b8;
  230. cursor: pointer;
  231. padding: 0;
  232. width: 20px;
  233. height: 20px;
  234. display: flex;
  235. align-items: center;
  236. justify-content: center;
  237. border-radius: 50%;
  238. transition: all 0.3s ease;
  239. }
  240. .tab-close:hover {
  241. background-color: #ef4444;
  242. color: #ffffff;
  243. }
  244. .tab-close:disabled {
  245. opacity: 0.5;
  246. cursor: not-allowed;
  247. }
  248. .tab-close:disabled:hover {
  249. background-color: transparent;
  250. color: #94a3b8;
  251. }
  252. /* 添加标签页按钮 */
  253. .tab-add {
  254. display: flex;
  255. align-items: center;
  256. justify-content: center;
  257. width: 36px;
  258. height: 36px;
  259. margin-left: 8px;
  260. background-color: #ffffff;
  261. border: 1px dashed #cbd5e1;
  262. border-radius: 6px;
  263. cursor: pointer;
  264. font-size: 18px;
  265. color: #94a3b8;
  266. transition: all 0.3s ease;
  267. }
  268. .tab-add:hover {
  269. border-color: #3b82f6;
  270. color: #3b82f6;
  271. background-color: #f0f9ff;
  272. }
  273. /* 标签页内容样式 */
  274. .tabs-content {
  275. flex: 1;
  276. overflow: auto;
  277. padding: 20px;
  278. background-color: #ffffff;
  279. }
  280. /* 过渡动画 */
  281. .fade-enter-active,
  282. .fade-leave-active {
  283. transition: opacity 0.3s ease;
  284. }
  285. .fade-enter-from,
  286. .fade-leave-to {
  287. opacity: 0;
  288. }
  289. /* 响应式设计 */
  290. @media (max-width: 768px) {
  291. .tabs-header {
  292. padding: 0 8px;
  293. }
  294. .tab-item {
  295. padding: 0 12px;
  296. margin-right: 4px;
  297. }
  298. .tab-title {
  299. font-size: 13px;
  300. }
  301. .tabs-content {
  302. padding: 16px;
  303. }
  304. }
  305. </style>