tabs.ts 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. import { defineStore } from 'pinia';
  2. import { ref, computed, markRaw, type Component } from 'vue';
  3. // 标签页类型
  4. export type TabType = 'page' | 'browser';
  5. // 标签页接口
  6. export interface Tab {
  7. id: string;
  8. title: string;
  9. type: TabType;
  10. // 页面类型标签页的路由路径
  11. path?: string;
  12. // 页面组件(用于缓存)
  13. component?: Component;
  14. // 浏览器类型标签页的数据
  15. browserData?: {
  16. platform: string;
  17. sessionId?: string;
  18. url?: string;
  19. cookieData?: string; // 预设的 Cookie 数据(JSON 格式)
  20. status?: 'loading' | 'ready' | 'login_pending' | 'login_success' | 'login_failed';
  21. groupId?: number;
  22. isAdminMode?: boolean; // 是否是管理后台模式(从后台按钮打开,不需要登录检测和保存账号)
  23. };
  24. // 是否可关闭
  25. closable?: boolean;
  26. // 图标
  27. icon?: string;
  28. }
  29. // 页面路由配置
  30. export const PAGE_CONFIG: Record<string, { title: string; icon: string }> = {
  31. '/': { title: '数据看板', icon: 'DataAnalysis' },
  32. '/accounts': { title: '账号管理', icon: 'User' },
  33. '/works': { title: '作品管理', icon: 'Film' },
  34. '/publish': { title: '发布管理', icon: 'Upload' },
  35. '/comments': { title: '评论管理', icon: 'ChatDotRound' },
  36. '/schedule': { title: '定时任务', icon: 'Clock' },
  37. '/analytics/overview': { title: '数据总览', icon: 'TrendCharts' },
  38. '/analytics/platform': { title: '平台数据', icon: 'TrendCharts' },
  39. '/analytics/account': { title: '账号数据', icon: 'TrendCharts' },
  40. '/analytics/work': { title: '作品数据', icon: 'TrendCharts' },
  41. '/settings': { title: '系统设置', icon: 'Setting' },
  42. '/profile': { title: '个人中心', icon: 'User' },
  43. };
  44. export const useTabsStore = defineStore('tabs', () => {
  45. // 所有标签页
  46. const tabs = ref<Tab[]>([]);
  47. // 当前激活的标签页ID
  48. const activeTabId = ref<string>('');
  49. // 账号列表刷新计数器(当计数器变化时,Accounts 页面会刷新)
  50. const accountRefreshTrigger = ref(0);
  51. // 当前激活的标签页
  52. const activeTab = computed(() =>
  53. tabs.value.find(tab => tab.id === activeTabId.value)
  54. );
  55. // 浏览器类型的标签页
  56. const browserTabs = computed(() =>
  57. tabs.value.filter(tab => tab.type === 'browser')
  58. );
  59. // 页面类型的标签页
  60. const pageTabs = computed(() =>
  61. tabs.value.filter(tab => tab.type === 'page')
  62. );
  63. // 生成唯一ID
  64. function generateId(): string {
  65. return `tab_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  66. }
  67. // 添加标签页
  68. function addTab(tab: Omit<Tab, 'id'> & { id?: string }): Tab {
  69. const newTab: Tab = {
  70. ...tab,
  71. id: tab.id || generateId(),
  72. closable: tab.closable ?? true,
  73. };
  74. // 检查是否已存在相同路径的页面标签
  75. if (tab.type === 'page' && tab.path) {
  76. const existingTab = tabs.value.find(t => t.type === 'page' && t.path === tab.path);
  77. if (existingTab) {
  78. activeTabId.value = existingTab.id;
  79. return existingTab;
  80. }
  81. }
  82. tabs.value.push(newTab);
  83. activeTabId.value = newTab.id;
  84. return newTab;
  85. }
  86. // 打开页面标签页
  87. function openPageTab(path: string, component?: Component): Tab {
  88. // 检查是否已存在(支持动态路由匹配)
  89. const existingTab = tabs.value.find(t => {
  90. if (t.type === 'page' && t.path) {
  91. // 精确匹配
  92. if (t.path === path) return true;
  93. // 动态路由匹配:/analytics/platform-detail/:platform
  94. if (path.startsWith('/analytics/platform-detail/') &&
  95. t.path?.startsWith('/analytics/platform-detail/')) {
  96. return true;
  97. }
  98. }
  99. return false;
  100. });
  101. if (existingTab) {
  102. // 如果路径不同,更新路径(用于动态路由)
  103. if (existingTab.path !== path) {
  104. existingTab.path = path;
  105. }
  106. activeTabId.value = existingTab.id;
  107. return existingTab;
  108. }
  109. // 获取页面配置(支持动态路由)
  110. let config = PAGE_CONFIG[path];
  111. if (!config && path.startsWith('/analytics/platform-detail/')) {
  112. // 从路径中提取平台名称
  113. const platform = path.split('/').pop() || '';
  114. const platformNames: Record<string, string> = {
  115. 'douyin': '抖音',
  116. 'xiaohongshu': '小红书',
  117. 'baijiahao': '百家号',
  118. 'weixin_video': '视频号',
  119. };
  120. config = {
  121. title: `${platformNames[platform] || platform}平台数据详情`,
  122. icon: 'TrendCharts'
  123. };
  124. }
  125. const finalConfig = config || { title: '未知页面', icon: 'Document' };
  126. const tab = addTab({
  127. title: finalConfig.title,
  128. type: 'page',
  129. path,
  130. component: component ? markRaw(component) : undefined,
  131. icon: finalConfig.icon,
  132. // 首页不可关闭
  133. closable: path !== '/',
  134. });
  135. return tab;
  136. }
  137. // 打开浏览器标签页
  138. function openBrowserTab(platform: string, title?: string, groupId?: number, url?: string, cookieData?: string, isAdminMode?: boolean): Tab {
  139. const tab = addTab({
  140. title: title || `${platform} 浏览器`,
  141. type: 'browser',
  142. browserData: {
  143. platform,
  144. status: 'loading',
  145. groupId,
  146. url, // 可选的初始 URL
  147. cookieData, // 可选的预设 Cookie
  148. isAdminMode, // 是否是管理后台模式
  149. },
  150. icon: 'Monitor',
  151. });
  152. return tab;
  153. }
  154. // 更新标签页
  155. function updateTab(id: string, updates: Partial<Tab>) {
  156. const index = tabs.value.findIndex(tab => tab.id === id);
  157. if (index !== -1) {
  158. tabs.value[index] = { ...tabs.value[index], ...updates };
  159. }
  160. }
  161. // 更新浏览器标签页数据
  162. function updateBrowserTab(id: string, browserData: Partial<Tab['browserData']>) {
  163. const tab = tabs.value.find(t => t.id === id);
  164. if (tab && tab.browserData) {
  165. tab.browserData = { ...tab.browserData, ...browserData };
  166. }
  167. }
  168. // 关闭标签页
  169. function closeTab(id: string) {
  170. const index = tabs.value.findIndex(tab => tab.id === id);
  171. if (index === -1) return;
  172. const tab = tabs.value[index];
  173. if (!tab.closable) return;
  174. tabs.value.splice(index, 1);
  175. // 如果关闭的是当前激活的标签页,激活相邻的标签页
  176. if (activeTabId.value === id) {
  177. if (tabs.value.length > 0) {
  178. // 激活前一个或后一个标签页
  179. const newIndex = Math.min(index, tabs.value.length - 1);
  180. activeTabId.value = tabs.value[newIndex].id;
  181. } else {
  182. activeTabId.value = '';
  183. }
  184. }
  185. }
  186. // 关闭所有标签页
  187. function closeAllTabs() {
  188. tabs.value = tabs.value.filter(tab => !tab.closable);
  189. if (tabs.value.length > 0) {
  190. activeTabId.value = tabs.value[0].id;
  191. } else {
  192. activeTabId.value = '';
  193. }
  194. }
  195. // 关闭其他标签页
  196. function closeOtherTabs(id: string) {
  197. tabs.value = tabs.value.filter(tab => tab.id === id || !tab.closable);
  198. activeTabId.value = id;
  199. }
  200. // 关闭右侧标签页
  201. function closeRightTabs(id: string) {
  202. const index = tabs.value.findIndex(tab => tab.id === id);
  203. if (index === -1) return;
  204. tabs.value = tabs.value.filter((tab, i) => i <= index || !tab.closable);
  205. // 如果当前激活的标签页被关闭了,激活指定的标签页
  206. if (!tabs.value.find(t => t.id === activeTabId.value)) {
  207. activeTabId.value = id;
  208. }
  209. }
  210. // 激活标签页
  211. function activateTab(id: string) {
  212. if (tabs.value.find(tab => tab.id === id)) {
  213. activeTabId.value = id;
  214. }
  215. }
  216. // 通过路径激活标签页
  217. function activateTabByPath(path: string): boolean {
  218. const tab = tabs.value.find(t => t.type === 'page' && t.path === path);
  219. if (tab) {
  220. activeTabId.value = tab.id;
  221. return true;
  222. }
  223. return false;
  224. }
  225. // 通过浏览器会话ID查找标签页
  226. function findTabBySessionId(sessionId: string): Tab | undefined {
  227. return tabs.value.find(
  228. tab => tab.type === 'browser' && tab.browserData?.sessionId === sessionId
  229. );
  230. }
  231. // 获取当前激活的页面路径
  232. function getActivePagePath(): string | undefined {
  233. const tab = activeTab.value;
  234. if (tab?.type === 'page') {
  235. return tab.path;
  236. }
  237. return undefined;
  238. }
  239. // 触发账号列表刷新
  240. function triggerAccountRefresh() {
  241. accountRefreshTrigger.value++;
  242. }
  243. return {
  244. // 状态
  245. tabs,
  246. activeTabId,
  247. accountRefreshTrigger,
  248. // 计算属性
  249. activeTab,
  250. browserTabs,
  251. pageTabs,
  252. // 方法
  253. addTab,
  254. openPageTab,
  255. openBrowserTab,
  256. updateTab,
  257. updateBrowserTab,
  258. closeTab,
  259. closeAllTabs,
  260. closeOtherTabs,
  261. closeRightTabs,
  262. activateTab,
  263. activateTabByPath,
  264. findTabBySessionId,
  265. getActivePagePath,
  266. triggerAccountRefresh,
  267. };
  268. });