WorkTab.vue 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. <template>
  2. <div class="work-tab-container">
  3. <div class="work-tabs">
  4. <a-tabs v-model:activeKey="activeKey" type="editable-card" :hide-add="true" @edit="onEdit" @change="onChange">
  5. <a-tab-pane v-for="tab in tabs" :key="tab.key" :tab="tab.title" :closable="tab.closable" />
  6. </a-tabs>
  7. </div>
  8. </div>
  9. </template>
  10. <script setup>
  11. import { ref, watch, onMounted, nextTick, defineExpose, defineProps } from 'vue';
  12. import { useRouter, useRoute } from 'vue-router';
  13. import CurdWindowResourceV2 from '../api/dic/CurdWindowResourceV2.js';
  14. import { menuClickedEvent } from '../common/eventBus.js';
  15. import { Uuid } from 'pc-component-v3';
  16. const props = defineProps({
  17. menuWidth: {
  18. type: Number,
  19. default: 230,
  20. },
  21. });
  22. const router = useRouter();
  23. const route = useRoute();
  24. // 标签页数组
  25. const tabs = ref([
  26. {
  27. key: '/desktop/dashboard',
  28. title: '首页',
  29. path: '/desktop/dashboard',
  30. closable: false, // 首页不可关闭
  31. },
  32. ]);
  33. // 当前激活的标签页
  34. const activeKey = ref('/desktop/dashboard');
  35. // 菜单节点映射表 - 存储路径与菜单节点的对应关系
  36. const pathMenuMap = ref({});
  37. // 初始化标签页
  38. onMounted(() => {
  39. // 尝试从本地存储中恢复标签页
  40. const savedTabs = localStorage.getItem('workTabs');
  41. if (savedTabs) {
  42. try {
  43. const parsedTabs = JSON.parse(savedTabs);
  44. if (Array.isArray(parsedTabs) && parsedTabs.length > 0) {
  45. // 确保首页总是存在
  46. if (!parsedTabs.some(tab => tab.path === '/desktop/dashboard')) {
  47. parsedTabs.unshift({
  48. key: '/desktop/dashboard',
  49. title: '首页',
  50. path: '/desktop/dashboard',
  51. closable: false,
  52. });
  53. }
  54. tabs.value = parsedTabs;
  55. }
  56. } catch (e) {
  57. console.error('恢复标签页失败', e);
  58. }
  59. }
  60. // 如果当前路由不是首页,添加当前路由的标签页
  61. if (route.path !== '/desktop/dashboard') {
  62. setTimeout(() => {
  63. addTab(route.path);
  64. }, 100);
  65. }
  66. });
  67. // 添加标签页方法
  68. const addTab = async path => {
  69. // 如果标签页已存在,则激活该标签页
  70. const existTab = tabs.value.find(tab => tab.path === path);
  71. if (existTab) {
  72. activeKey.value = existTab.key;
  73. return;
  74. }
  75. // 尝试获取菜单标题
  76. let title = await getMenuTitle(path);
  77. // if (!title) {
  78. // // 如果没有找到菜单标题,则从路由元数据中获取
  79. // const matchedRoute = router.getRoutes().find(route => route.path === path);
  80. // if (matchedRoute && matchedRoute.meta && matchedRoute.meta.title) {
  81. // title = matchedRoute.meta.title;
  82. // } else {
  83. // // 如果路由也没有定义标题,则从路径中提取
  84. // const pathSegments = path.split('/');
  85. // title = pathSegments[pathSegments.length - 1] || '未命名页面';
  86. // }
  87. // }
  88. if (title) {
  89. // 创建新标签页
  90. const newTab = {
  91. key: path,
  92. title: title,
  93. path: path,
  94. closable: true, // 非首页标签可以关闭
  95. };
  96. tabs.value.push(newTab);
  97. activeKey.value = path;
  98. // 保存标签页到本地存储
  99. saveTabsToLocalStorage();
  100. }
  101. };
  102. // 获取菜单标题
  103. const getMenuTitle = async path => {
  104. // 如果有缓存的菜单信息,直接使用
  105. if (pathMenuMap.value[path]) {
  106. return pathMenuMap.value[path].title;
  107. }
  108. // 处理不同类型的路由
  109. if (path.includes('/desktop/window/') || path.includes('/desktop/window1/')) {
  110. const windowNo = extractWindowNo(path);
  111. if (windowNo) {
  112. try {
  113. const res = await CurdWindowResourceV2.uniqueByNoAccessControl(windowNo);
  114. if (res.errorCode === 0 && res.data) {
  115. return res.data.name;
  116. }
  117. } catch (error) {
  118. console.error('获取窗口标题失败', error);
  119. }
  120. }
  121. } else if (path.includes('/desktop/sheetWindow/')) {
  122. const windowNo = extractWindowNo(path);
  123. if (windowNo) {
  124. // 这里可以添加获取SheetWindow标题的逻辑
  125. return `表格窗口-${windowNo}`;
  126. }
  127. } else if (path.includes('/desktop/info/')) {
  128. const infoWindowNo = extractWindowNo(path);
  129. if (infoWindowNo) {
  130. // 这里可以添加获取Info窗口标题的逻辑
  131. return `信息窗口-${infoWindowNo}`;
  132. }
  133. }
  134. return '';
  135. };
  136. // 从路径中提取窗口编号
  137. const extractWindowNo = path => {
  138. const segments = path.split('/');
  139. for (let i = 0; i < segments.length; i++) {
  140. if (segments[i] === 'window' || segments[i] === 'window1' || segments[i] === 'sheetWindow' || segments[i] === 'info') {
  141. return segments[i + 1]?.split('?')[0];
  142. }
  143. }
  144. return null;
  145. };
  146. // 编辑标签页(关闭)
  147. const onEdit = (targetKey, action) => {
  148. if (action === 'remove') {
  149. removeTab(targetKey);
  150. }
  151. };
  152. // 移除标签页
  153. const removeTab = targetKey => {
  154. // 找到当前关闭的标签页索引
  155. const targetIndex = tabs.value.findIndex(tab => tab.key === targetKey);
  156. // 从数组中移除该标签页
  157. tabs.value = tabs.value.filter(tab => tab.key !== targetKey);
  158. // 如果关闭的是当前激活的标签页,则需要激活其他标签页
  159. if (targetKey === activeKey.value) {
  160. // 如果关闭的是最后一个标签页,则激活前一个标签页
  161. if (targetIndex === tabs.value.length) {
  162. activeKey.value = tabs.value[tabs.value.length - 1].key;
  163. } else {
  164. // 否则激活后一个标签页
  165. activeKey.value = tabs.value[targetIndex].key;
  166. }
  167. console.log(activeKey.value, 'activeKey.value');
  168. // 跳转到新激活标签页对应的路由
  169. if (activeKey.value.includes('/desktop/window1/')) {
  170. router.push(activeKey.value + '?uuid=' + Uuid.createUUID());
  171. } else {
  172. router.push(activeKey.value);
  173. }
  174. }
  175. // 保存标签页到本地存储
  176. saveTabsToLocalStorage();
  177. };
  178. // 切换标签页
  179. const onChange = newActiveKey => {
  180. let uidWindow = newActiveKey;
  181. if (newActiveKey.includes('/desktop/window1/')) {
  182. uidWindow = newActiveKey + '?uuid=' + Uuid.createUUID();
  183. }
  184. activeKey.value = newActiveKey;
  185. router.push(uidWindow);
  186. };
  187. // 保存标签页到本地存储
  188. const saveTabsToLocalStorage = () => {
  189. try {
  190. localStorage.setItem('workTabs', JSON.stringify(tabs.value));
  191. } catch (e) {
  192. console.error('保存标签页失败', e);
  193. }
  194. };
  195. // 监听菜单点击事件,添加菜单信息到pathMenuMap
  196. watch(menuClickedEvent, event => {
  197. if (event && event.path && event.menuInfo) {
  198. pathMenuMap.value[event.path] = event.menuInfo;
  199. }
  200. }, { deep: true, immediate: true });
  201. // 监听路由变化,添加新标签页
  202. watch(
  203. () => route.path,
  204. newPath => {
  205. // 有一些特殊路由可能不需要添加标签页,可以在这里过滤
  206. const ignorePaths = ['/login', '/loginNode', '/loginGraphic'];
  207. if (!ignorePaths.includes(newPath)) {
  208. nextTick(() => {
  209. setTimeout(() => {
  210. addTab(newPath);
  211. }, 100);
  212. });
  213. }
  214. },
  215. );
  216. watch(
  217. () => props.menuWidth,
  218. newWidth => {
  219. nextTick(() => {
  220. const workTabContainer = document.querySelector('.work-tab-container');
  221. if (workTabContainer) {
  222. setTimeout(() => {
  223. workTabContainer.style.width = `calc(100% - 20px - ${newWidth}px)`;
  224. });
  225. }
  226. });
  227. },
  228. { immediate: true },
  229. );
  230. // 供外部组件调用,用于添加带有菜单信息的标签页
  231. const addTabWithMenuInfo = (path, menuInfo) => {
  232. // 缓存菜单信息
  233. pathMenuMap.value[path] = {
  234. title: menuInfo.title || menuInfo.name || '',
  235. menuNodeType: menuInfo.menuNodeType,
  236. no: menuInfo.no,
  237. };
  238. // 添加标签页
  239. addTab(path);
  240. };
  241. // 暴露方法给其他组件使用
  242. defineExpose({
  243. addTabWithMenuInfo,
  244. });
  245. </script>
  246. <style scoped>
  247. .work-tab-container {
  248. width: calc(100% - 20px - 230px);
  249. background-color: #fff;
  250. padding: 0;
  251. position: fixed;
  252. top: 50px;
  253. right: 0;
  254. z-index: 10;
  255. }
  256. .work-tabs {
  257. overflow-x: auto;
  258. overflow-y: hidden;
  259. scrollbar-width: none;
  260. /* Firefox */
  261. -ms-overflow-style: none;
  262. /* IE and Edge */
  263. }
  264. .work-tabs::-webkit-scrollbar {
  265. display: none;
  266. /* Chrome, Safari, Opera */
  267. }
  268. /* 添加额外的样式来确保内容区域不被遮挡 */
  269. :deep(.ant-tabs) {
  270. height: 40px;
  271. /* 设置固定高度 */
  272. }
  273. :deep(.ant-tabs-nav) {
  274. margin-bottom: 0;
  275. }
  276. :deep(.ant-tabs-nav-wrap) {
  277. flex: 1;
  278. overflow-x: auto;
  279. scrollbar-width: none;
  280. -ms-overflow-style: none;
  281. }
  282. :deep(.ant-tabs-nav-wrap::-webkit-scrollbar) {
  283. display: none;
  284. }
  285. :deep(.ant-tabs-tab) {
  286. padding: 6px 16px !important;
  287. }
  288. :deep(.ant-tabs-tab-btn) {
  289. transition: all 0.3s;
  290. }
  291. :deep(.ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn) {
  292. color: #1890ff;
  293. font-weight: 500;
  294. }
  295. :deep(.ant-tabs-extra-content) {
  296. line-height: 44px;
  297. }
  298. </style>