index.vue 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. <template>
  2. <div class="tabs">
  3. <el-scrollbar
  4. class="scroll-container tags-view-container"
  5. ref="scrollbarDom"
  6. @wheel.passive="handleWhellScroll"
  7. @scroll="handleScroll"
  8. >
  9. <Item
  10. v-for="menu in menuList"
  11. :key="menu.meta.title"
  12. :menu="menu"
  13. :active="activeMenu.path === menu.path"
  14. @close="delMenu(menu)"
  15. @reload="pageReload"
  16. />
  17. </el-scrollbar>
  18. <div class="handle">
  19. <div id="vueAdminBoxTabRefresh" @click="pageReload"></div>
  20. <div id="vueAdminBoxTabCloseSelf" @click="closeCurrentRoute"></div>
  21. <div id="vueAdminBoxTabCloseOther" @click="closeOtherRoute"></div>
  22. <div id="vueAdminBoxTabCloseAll" @click="closeAllRoute"></div>
  23. <el-dropdown placement="bottom">
  24. <div class="el-dropdown-link">
  25. <el-icon><ArrowDown /></el-icon>
  26. </div>
  27. <template #dropdown>
  28. <el-dropdown-menu>
  29. <el-dropdown-item class="tab-ddropdown-item" :icon="RefreshLeft" @click="pageReload">重新加载</el-dropdown-item>
  30. <el-dropdown-item class="tab-ddropdown-item" :icon="CircleClose" :disabled="currentDisabled" @click="closeCurrentRoute">关闭当前标签</el-dropdown-item>
  31. <el-dropdown-item class="tab-ddropdown-item" :icon="CircleClose" :disabled="menuList.length < 3" @click="closeOtherRoute">关闭其他标签</el-dropdown-item>
  32. <el-dropdown-item class="tab-ddropdown-item" :icon="CircleClose" :disabled="menuList.length <= 1" @click="closeAllRoute">关闭所有标签</el-dropdown-item>
  33. </el-dropdown-menu>
  34. </template>
  35. </el-dropdown>
  36. <el-tooltip class="item" effect="dark" :content="contentFullScreen ? '退出全屏': '全屏'" placement="bottom">
  37. <el-icon @click="onFullscreen"><FullScreen /></el-icon>
  38. </el-tooltip>
  39. </div>
  40. </div>
  41. </template>
  42. <script lang="js">
  43. /** 引用vue系列函数 */
  44. import { defineComponent, computed, unref, watch, reactive, ref, nextTick } from 'vue'
  45. import { useStore } from 'vuex'
  46. import { useRoute, useRouter } from 'vue-router'
  47. /** 引用图标 */
  48. import { ArrowDown, RefreshLeft, CircleClose, FullScreen } from '@element-plus/icons'
  49. import Item from './item.vue'
  50. import tabsHook from './tabsHook'
  51. export default defineComponent({
  52. components: {
  53. Item, ArrowDown, FullScreen
  54. },
  55. setup() {
  56. const store = useStore()
  57. const route = useRoute()
  58. const router = useRouter()
  59. const scrollbarDom = ref(null)
  60. const scrollLeft = ref(0)
  61. const defaultMenu = {
  62. path: '/dashboard',
  63. meta: { title: '首页', hideClose: true }
  64. }
  65. const contentFullScreen = computed(() => store.state.app.contentFullScreen)
  66. const currentDisabled = computed(() => route.path === defaultMenu.path)
  67. let activeMenu = reactive({ path: '' })
  68. let menuList = ref(tabsHook.getItem())
  69. if (menuList.value.length === 0) { // 判断之前有没有调用过
  70. addMenu(defaultMenu)
  71. }
  72. watch(menuList.value, (newVal) => {
  73. tabsHook.setItem(newVal)
  74. })
  75. watch(menuList, (newVal) => {
  76. tabsHook.setItem(newVal)
  77. })
  78. router.afterEach(() => {
  79. addMenu(route)
  80. initMenu(route)
  81. })
  82. // 全屏
  83. function onFullscreen() {
  84. store.commit('app/contentFullScreenChange', !contentFullScreen.value)
  85. }
  86. // 当前页面组件重新加载
  87. function pageReload() {
  88. const self = route.matched[route.matched.length-1].instances.default
  89. self.handleReload();
  90. }
  91. // 关闭当前标签,首页不关闭
  92. function closeCurrentRoute() {
  93. if (route.path !== defaultMenu.path) {
  94. const tab = document.getElementById('vueAdminBoxTabCloseSelf')
  95. const nextPath = tab?.getAttribute('nextPath')
  96. delMenu(route, nextPath)
  97. }
  98. }
  99. // 关闭除了当前标签之外的所有标签
  100. function closeOtherRoute() {
  101. menuList.value = [defaultMenu]
  102. if (route.path !== defaultMenu.path) {
  103. addMenu(route)
  104. }
  105. setKeepAliveData()
  106. }
  107. // 关闭所有的标签,除了首页
  108. function closeAllRoute() {
  109. menuList.value = [defaultMenu]
  110. setKeepAliveData()
  111. router.push(defaultMenu.path)
  112. }
  113. // 添加新的菜单项
  114. function addMenu(menu) {
  115. let { path, meta, name, query } = menu
  116. if (meta.hideTabs) {
  117. return
  118. }
  119. let hasMenu = menuList.value.some((obj) => {
  120. return obj.path === path
  121. })
  122. if (!hasMenu) {
  123. menuList.value.push({
  124. path,
  125. meta,
  126. name,
  127. query
  128. })
  129. }
  130. }
  131. // 删除菜单项
  132. function delMenu(menu, nextPath) {
  133. let index = 0
  134. if (!menu.meta.hideClose) {
  135. if (menu.meta.cache && menu.name) {
  136. store.commit('keepAlive/delKeepAliveComponentsName', menu.name)
  137. }
  138. index = menuList.value.findIndex((item) => item.path === menu.path)
  139. menuList.value.splice(index, 1)
  140. }
  141. if (nextPath) {
  142. router.push(nextPath)
  143. return
  144. }
  145. // 若删除的是当前页面,回到前一页,若为最后一页,则回到默认的首页
  146. if (menu.path === activeMenu.path) {
  147. const prePage = index - 1 > 0 ? menuList.value[index - 1] : { path: defaultMenu.path }
  148. router.push({ path: prePage.path, query: prePage.query || {} })
  149. }
  150. }
  151. // 初始化activeMenu
  152. function initMenu(menu) {
  153. activeMenu = menu
  154. nextTick(() => {
  155. setPosition()
  156. })
  157. }
  158. /** 设置当前滚动条应该在的位置 */
  159. function setPosition() {
  160. if (scrollbarDom.value) {
  161. const domBox = {
  162. scrollbar: scrollbarDom.value.wrapRef,
  163. activeDom: scrollbarDom.value.wrapRef.querySelector('.active'),
  164. activeFather: scrollbarDom.value.wrapRef.querySelector('.el-scrollbar__view')
  165. }
  166. let hasDoms = true
  167. Object.keys(domBox).forEach((dom) => {
  168. if (!dom) {
  169. hasDoms = false
  170. }
  171. })
  172. if (!hasDoms) {
  173. return
  174. }
  175. const domData = {
  176. scrollbar: domBox.scrollbar.getBoundingClientRect(),
  177. activeDom: domBox.activeDom.getBoundingClientRect(),
  178. activeFather: domBox.activeFather.getBoundingClientRect()
  179. }
  180. const num = domData.activeDom.x - domData.activeFather.x + 1/2 * domData.activeDom.width - 1/2 * domData.scrollbar.width
  181. domBox.scrollbar.scrollLeft = num
  182. }
  183. }
  184. // 配置需要缓存的数据
  185. function setKeepAliveData() {
  186. let keepAliveNames = []
  187. menuList.value.forEach((menu) => {
  188. menu.meta && menu.meta.cache && menu.name && keepAliveNames.push(menu.name)
  189. })
  190. store.commit('keepAlive/setKeepAliveComponentsName', keepAliveNames)
  191. }
  192. /** 监听鼠标滚动事件 */
  193. function handleWhellScroll(e) {
  194. let distance = 0
  195. let speed = 5
  196. if (e.wheelDelta > 30) {
  197. distance = -10
  198. } else if (e.wheelDelta < -30) {
  199. distance = 10
  200. }
  201. // console.log(scrollLeft.value + eventDelta / 4)
  202. scrollbarDom.value?.setScrollLeft(scrollLeft.value + distance * speed)
  203. }
  204. /** 监听滚动事件 */
  205. function handleScroll({ scrollLeft: left }) {
  206. scrollLeft.value = left
  207. }
  208. // 初始化时调用:1. 新增菜单 2. 初始化activeMenu
  209. addMenu(route)
  210. initMenu(route)
  211. return {
  212. RefreshLeft, CircleClose,
  213. contentFullScreen,
  214. scrollbarDom,
  215. // 菜单相关
  216. menuList,
  217. activeMenu,
  218. currentDisabled,
  219. onFullscreen,
  220. pageReload,
  221. delMenu,
  222. closeCurrentRoute,
  223. closeOtherRoute,
  224. closeAllRoute,
  225. handleScroll,
  226. handleWhellScroll
  227. }
  228. }
  229. })
  230. </script>
  231. <style lang="scss" scoped>
  232. .tabs {
  233. display: flex;
  234. justify-content: space-between;
  235. align-items: center;
  236. height: 40px;
  237. background: var(--system-header-background);
  238. border-bottom: 1px solid var(--system-header-border-color);
  239. border-top: 1px solid var(--system-header-border-color);
  240. box-shadow: 0 1px 4px 0 rgba(0, 0, 0, .1);
  241. .handle {
  242. min-width: 95px;
  243. height: 100%;
  244. display: flex;
  245. align-items: center;
  246. .el-dropdown-link {
  247. margin-top: 5px;
  248. border-left: 1px solid var(--system-header-border-color);
  249. height: 25px;
  250. width: 40px;
  251. display: flex;
  252. justify-content: center;
  253. align-items: center;
  254. }
  255. i {
  256. color: var(--system-header-text-color);
  257. }
  258. }
  259. }
  260. .scroll-container {
  261. white-space: nowrap;
  262. position: relative;
  263. overflow: hidden;
  264. width: 100%;
  265. :deep(.el-scrollbar__bar) {
  266. bottom: 0px;
  267. }
  268. :deep(.el-scrollbar__wrap) {
  269. height: 49px;
  270. }
  271. }
  272. .tags-view-container {
  273. height: 34px;
  274. flex: 1;
  275. width: 100%;
  276. display: flex;
  277. }
  278. .el-icon-full-screen {
  279. cursor: pointer;
  280. &:hover {
  281. background: rgba(0,0,0,.025);
  282. }
  283. &:focus {
  284. outline: none;
  285. }
  286. }
  287. .tab-ddropdown-item {
  288. display: flex;
  289. align-items: center;
  290. }
  291. </style>