tabs.vue 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. <script lang="ts">
  2. import SvgIcon from "@/components/base/svg-icon";
  3. import { EMitt } from "@/constants/enum";
  4. import { IObject } from "@/types/interface";
  5. import emits from "@/utils/emits";
  6. import { arrayToObject } from "@/utils/utils";
  7. import { ElMessage } from "element-plus";
  8. import { findIndex } from "lodash";
  9. import { defineComponent, reactive, watch } from "vue";
  10. import { RouteLocationMatched, useRouter } from "vue-router";
  11. import { useAppStore } from "@/store";
  12. /**
  13. * tab标签页
  14. */
  15. export default defineComponent({
  16. name: "Tabs",
  17. components: { SvgIcon },
  18. props: {
  19. tabs: Array,
  20. activeTabName: String
  21. },
  22. setup(props) {
  23. const ops = [
  24. { label: "关闭当前标签页", value: 5, icon: "close" },
  25. { label: "关闭其他标签页", value: 1, icon: "close" },
  26. { label: "关闭全部标签页", value: 4, icon: "circle-close" }
  27. ];
  28. const router = useRouter();
  29. const store = useAppStore();
  30. const firstRoute = (router.options.routes[0] || {}) as RouteLocationMatched;
  31. const home: RouteLocationMatched = firstRoute.children && firstRoute.children.length > 0 ? (firstRoute.children[0] as RouteLocationMatched) : firstRoute;
  32. const defaultTab = { label: "", value: home.path };
  33. const state = reactive({
  34. activeTabName: props.activeTabName || defaultTab.value,
  35. tabs: (props.tabs && props.tabs.length ? props.tabs : [defaultTab]) as IObject[]
  36. });
  37. watch(
  38. () => state.tabs,
  39. (res) => {
  40. store.updateState({ tabs: res });
  41. },
  42. { deep: true }
  43. );
  44. emits.on(EMitt.OnPushMenuToTabs, (route) => {
  45. const path: string = route.value;
  46. if (path.includes("/error")) {
  47. return;
  48. }
  49. const tabKeys: IObject<number> = arrayToObject(state.tabs, "value", () => 1);
  50. if (!tabKeys[path]) {
  51. state.tabs.push(route);
  52. }
  53. if (state.activeTabName !== path) {
  54. state.activeTabName = path;
  55. }
  56. });
  57. emits.on(EMitt.OnCloseCurrTab, () => {
  58. onClose(5);
  59. });
  60. const onTabClick = (tab: any) => {
  61. tab.props.name && router.push(tab.props.name);
  62. };
  63. const onTabRemove = (targetName: string) => {
  64. const index = findIndex(state.tabs, (x) => x.value === targetName);
  65. if (state.tabs.length > 1) {
  66. updateClosedTabs([...store.state.closedTabs, targetName], false);
  67. if (state.activeTabName === targetName) {
  68. const toIndex = index === 0 ? index + 1 : index - 1;
  69. state.activeTabName = state.tabs[toIndex].value;
  70. router.push(state.activeTabName);
  71. }
  72. state.tabs.splice(index, 1);
  73. } else {
  74. ElMessage({ type: "error", message: "只剩下一个标签页,不支持关闭", offset: 0 });
  75. }
  76. };
  77. const updateClosedTabs = (closedTabs: any[], isTransform = true) => {
  78. if (isTransform) {
  79. closedTabs = closedTabs.map((x) => x.value);
  80. }
  81. store.updateState({ closedTabs });
  82. };
  83. const onClose = (value: number) => {
  84. let index = null;
  85. const rawTabs = state.tabs;
  86. switch (value) {
  87. case 1:
  88. //其他
  89. state.tabs = state.tabs.filter((x) => [home.path, state.activeTabName].includes(x.value));
  90. updateClosedTabs(rawTabs.filter((x) => ![home.path, state.activeTabName].includes(x.value)));
  91. break;
  92. case 2:
  93. //右侧
  94. index = findIndex(state.tabs, (x) => x.value === state.activeTabName);
  95. state.tabs.splice(index + 1, state.tabs.length - (index + 1));
  96. updateClosedTabs(rawTabs.slice(index + 1));
  97. break;
  98. case 3:
  99. //左侧
  100. index = findIndex(state.tabs, (x) => x.value === state.activeTabName);
  101. state.tabs.splice(1, index - 1);
  102. updateClosedTabs(rawTabs.slice(1, index - 1));
  103. break;
  104. case 4:
  105. //全部
  106. state.tabs = [defaultTab];
  107. state.activeTabName = defaultTab.value;
  108. updateClosedTabs(rawTabs);
  109. router.push(state.activeTabName);
  110. break;
  111. case 5:
  112. //当前
  113. if (state.activeTabName !== defaultTab.value) {
  114. updateClosedTabs([...store.state.closedTabs, state.activeTabName], false);
  115. index = findIndex(state.tabs, (x) => x.value === state.activeTabName);
  116. state.tabs.splice(index, 1);
  117. state.activeTabName = state.tabs[state.tabs.length - 1].value;
  118. router.push(state.activeTabName);
  119. }
  120. break;
  121. default:
  122. break;
  123. }
  124. };
  125. return { state, onTabClick, onTabRemove, home, onClose, ops };
  126. }
  127. });
  128. </script>
  129. <template>
  130. <div class="rr-view-tab-wrap">
  131. <el-tabs class="rr-view-tab" v-model="state.activeTabName" @tab-click="onTabClick" @tab-remove="onTabRemove">
  132. <el-tab-pane :name="home.path" :closable="false">
  133. <template #label>
  134. <!-- 文字主页和图标主页tab -->
  135. <!-- {{ t("ui.router.pageHome") }} -->
  136. <svg-icon name="home"></svg-icon>
  137. </template>
  138. </el-tab-pane>
  139. <el-tab-pane v-for="x in state.tabs.slice(1)" :key="x.value" :label="x.label" :name="x.value" :closable="true"></el-tab-pane>
  140. </el-tabs>
  141. <el-dropdown trigger="click" placement="bottom-end" class="rr-view-tab-ops" @command="onClose">
  142. <template #dropdown>
  143. <el-dropdown-menu>
  144. <el-dropdown-item v-for="x in ops" :key="x.value" :icon="x.icon" :command="x.value">
  145. {{ x.label }}
  146. </el-dropdown-item>
  147. </el-dropdown-menu>
  148. </template>
  149. <span class="el-dropdown-link">
  150. <el-icon class="el-icon--right"><arrow-down /></el-icon>
  151. </span>
  152. </el-dropdown>
  153. </div>
  154. </template>