index.vue 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. <template>
  2. <div class="tjm_tabsbar_container">
  3. <el-scrollbar
  4. ref="scrollContainer"
  5. :vertical="false"
  6. class="tjm_tabs-content"
  7. @wheel.prevent="handleScroll"
  8. >
  9. <router-link
  10. v-for="tag in visitedViews"
  11. :key="tag.path"
  12. :data-path="tag.path"
  13. :class="isActive(tag) ? 'active' : ''"
  14. :to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
  15. class="tags-view-item"
  16. :style="activeStyle(tag)"
  17. @click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''"
  18. @contextmenu.prevent="openMenu(tag, $event)"
  19. >
  20. {{ tag.title }}
  21. <span v-if="!isAffix(tag)" @click.prevent.stop="closeSelectedTag(tag)">
  22. <close
  23. class="el-icon-close"
  24. style="width: 1em; height: 1em; vertical-align: middle"
  25. />
  26. </span>
  27. </router-link>
  28. </el-scrollbar>
  29. <ul
  30. v-show="visible"
  31. :style="{ left: left + 'px', top: top + 'px' }"
  32. class="contextmenu"
  33. >
  34. <li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">
  35. <close style="width: 1em; height: 1em;" /> 关闭当前
  36. </li>
  37. <li @click="closeOthersTags">
  38. <circle-close style="width: 1em; height: 1em;" /> 关闭其他
  39. </li>
  40. </ul>
  41. </div>
  42. </template>
  43. <script setup>
  44. import { useTabsBarStore } from '@/store/tabsbar.js'
  45. import { usePermissionStore } from '@/store/permission.js'
  46. import { useSettingStore } from '@/store/settings.js'
  47. const routes = computed(() => usePermissionStore().routes)
  48. const route = useRoute()
  49. const router = useRouter()
  50. //==================================================
  51. const visitedViews = computed(() => useTabsBarStore().visitedViews)
  52. const affixTags = ref([])
  53. const visible = ref(false)
  54. const theme = computed(() => useSettingStore().theme)
  55. const top = ref(0)
  56. const left = ref(0)
  57. const selectedTag = ref({})
  58. const { proxy } = getCurrentInstance()
  59. watch(route, () => {
  60. addTags()
  61. // moveToCurrentTag()
  62. })
  63. onMounted(() => {
  64. initTags()
  65. addTags()
  66. })
  67. function addTags() {
  68. const { name } = route
  69. if (name) {
  70. useTabsBarStore().addView(route)
  71. }
  72. return false
  73. }
  74. function initTags() {
  75. const res = filterAffixTags(routes.value)
  76. affixTags.value = res
  77. for (const tag of res) {
  78. if (tag.name) {
  79. useTabsBarStore().addVisitedView(tag)
  80. }
  81. }
  82. }
  83. function moveToCurrentTag() {
  84. nextTick(() => {
  85. for (const r of visitedViews.value) {
  86. if (r.path === route.path) {
  87. scrollPaneRef.value.moveToTarget(r)
  88. if (r.fullPath !== route.fullPath) {
  89. useTagsViewStore().updateVisitedView(route)
  90. }
  91. }
  92. }
  93. })
  94. }
  95. function filterAffixTags(routes, basePath = '') {
  96. let tags = []
  97. routes.forEach(route => {
  98. if (route.meta && route.meta.affix) {
  99. const tagPath = getNormalPath(basePath + '/' + route.path)
  100. tags.push({
  101. fullPath: tagPath,
  102. path: tagPath,
  103. name: route.name,
  104. meta: { ...route.meta }
  105. })
  106. }
  107. if (route.children) {
  108. const tempTags = filterAffixTags(route.children, route.path)
  109. if (tempTags.length >= 1) {
  110. tags = [...tags, ...tempTags]
  111. }
  112. }
  113. })
  114. return tags
  115. }
  116. function getNormalPath(p) {
  117. if (p.length === 0 || !p || p == 'undefined') {
  118. return p
  119. }
  120. let res = p.replace('//', '/')
  121. if (res[res.length - 1] === '/') {
  122. return res.slice(0, res.length - 1)
  123. }
  124. return res
  125. }
  126. function closeMenu() {
  127. visible.value = false
  128. }
  129. function handleScroll() {
  130. closeMenu()
  131. }
  132. watch(visible, (value) => {
  133. if (value) {
  134. document.body.addEventListener('click', closeMenu)
  135. } else {
  136. document.body.removeEventListener('click', closeMenu)
  137. }
  138. })
  139. function openMenu(tag, e) {
  140. const menuMinWidth = 105
  141. const offsetLeft = proxy.$el.getBoundingClientRect().left
  142. const offsetWidth = proxy.$el.offsetWidth // container width
  143. const maxLeft = offsetWidth - menuMinWidth // left boundary
  144. const l = e.clientX - offsetLeft + 15 // 15: margin right
  145. if (l > maxLeft) {
  146. left.value = maxLeft
  147. } else {
  148. left.value = l
  149. }
  150. top.value = 15
  151. visible.value = true
  152. selectedTag.value = tag
  153. }
  154. function isActive(r) {
  155. return r.path === decodeURIComponent(route.path)
  156. }
  157. function activeStyle(tag) {
  158. if (!isActive(tag)) return {}
  159. return {
  160. 'background-color': theme.value,
  161. 'border-color': theme.value
  162. }
  163. }
  164. function isAffix(tag) {
  165. // if(tag.name=="Index") return true
  166. return tag.meta && tag.meta.affix
  167. }
  168. function closeSelectedTag(view) {
  169. useTabsBarStore()
  170. .delView(view)
  171. .then(({ visitedViews }) => {
  172. if (isActive(view)) {
  173. toLastView(visitedViews, view)
  174. }
  175. })
  176. }
  177. function closeOthersTags() {
  178. useTabsBarStore().delOthersViews(selectedTag.value).then(({visitedViews})=>{
  179. toLastView(visitedViews, selectedTag.value)
  180. })
  181. }
  182. function toLastView(visitedViews, view) {
  183. const latestView = visitedViews.slice(-1)[0]
  184. if (latestView) {
  185. router.push(latestView.fullPath)
  186. } else {
  187. if (view.name === 'Index') {
  188. router.replace({ path: '/redirect' + view.fullPath })
  189. } else {
  190. router.push('/')
  191. }
  192. }
  193. }
  194. </script>
  195. <style lang="scss" scoped>
  196. .tjm_tabsbar_container {
  197. position: relative;
  198. height: $base-tabsbar-height;
  199. user-select: none;
  200. border-top: 1px solid #f6f6f6;
  201. display: flex;
  202. align-items: center;
  203. padding-right: $base-padding;
  204. padding-left: $base-padding;
  205. background: $base-color-white;
  206. white-space: nowrap;
  207. .tjm_tabs-content {
  208. height: 40px;
  209. width: 100%;
  210. // display: flex;
  211. // align-items: center;
  212. .tags-view-item {
  213. display: inline-block;
  214. position: relative;
  215. cursor: pointer;
  216. height: 26px;
  217. line-height: 26px;
  218. border: 1px solid #d8dce5;
  219. color: #495060;
  220. background: #fff;
  221. padding: 0 8px;
  222. font-size: 12px;
  223. margin-left: 5px;
  224. margin-top: 4px;
  225. text-decoration: none;
  226. &:hover {
  227. border: 1px solid #409eff;
  228. }
  229. &:first-of-type {
  230. margin-left: 15px;
  231. }
  232. &:last-of-type {
  233. margin-right: 15px;
  234. }
  235. &.active {
  236. background-color: #42b983;
  237. color: #fff;
  238. border-color: #42b983;
  239. &::before {
  240. content: '';
  241. background: #fff;
  242. display: inline-block;
  243. width: 8px;
  244. height: 8px;
  245. border-radius: 50%;
  246. position: relative;
  247. margin-right: 5px;
  248. }
  249. }
  250. }
  251. }
  252. .contextmenu {
  253. margin: 0;
  254. background: #fff;
  255. position: absolute;
  256. z-index: 3000;
  257. list-style-type: none;
  258. padding: 5px 0;
  259. border-radius: 4px;
  260. font-size: 12px;
  261. font-weight: 400;
  262. color: #333;
  263. box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
  264. li {
  265. margin: 0;
  266. padding: 7px 16px;
  267. cursor: pointer;
  268. &:hover {
  269. background: #eee;
  270. }
  271. }
  272. }
  273. }
  274. </style>
  275. <style lang="scss">
  276. .tjm_tabs-content {
  277. a {
  278. text-decoration: none;
  279. }
  280. .router-link-active {
  281. text-decoration: none;
  282. }
  283. .el-icon-close {
  284. width: 16px;
  285. height: 16px;
  286. vertical-align: 2px;
  287. border-radius: 50%;
  288. text-align: center;
  289. transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
  290. transform-origin: 100% 50%;
  291. &:before {
  292. transform: scale(0.6);
  293. display: inline-block;
  294. vertical-align: -3px;
  295. }
  296. &:hover {
  297. background-color: #b4bccc;
  298. color: #fff;
  299. width: 12px !important;
  300. height: 12px !important;
  301. }
  302. }
  303. }
  304. </style>