keepAlive.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. import { defineStore } from "pinia";
  2. import { KeepAliveState } from "@/stores/interface";
  3. import { cloneDeep } from "lodash-es";
  4. import { useTabsStore } from "@/stores/modules/tabs";
  5. import { LockBill, UnLockBill } from "@/api/modules/common";
  6. // 最大缓存页面数量限制
  7. const MAX_KEEP_ALIVE_COUNT = 10;
  8. export const useKeepAliveStore = defineStore({
  9. id: "geeker-keepAlive",
  10. state: (): KeepAliveState => ({
  11. keepAliveName: [],
  12. keepAliveList: [],
  13. closeAliveList: []
  14. }),
  15. getters: {
  16. getKeepAliveList(): any[] {
  17. return this.keepAliveList;
  18. }
  19. },
  20. actions: {
  21. setKeepAliveList(keepAliveList: any) {
  22. this.keepAliveList = keepAliveList;
  23. console.log("getKeepAliveName routeItem setKeepAliveList :>> ", JSON.stringify(this.keepAliveList));
  24. console.log("getKeepAliveName routeItem setKeepAliveList :>> ", this.keepAliveList);
  25. },
  26. // Add KeepAliveName
  27. async addKeepAliveName(route: any) {
  28. !this.keepAliveName.includes(route.name) && this.keepAliveName.push(route.name);
  29. let routeItem = this.keepAliveList.find((item: any) => item.fullPath == route.fullPath);
  30. console.log("getKeepAliveName routeItem addKeepAliveName :>> ", JSON.stringify(this.keepAliveList));
  31. console.log("getKeepAliveName routeItem removeKeepAliveName closeAliveList:>> ", JSON.stringify(this.closeAliveList));
  32. if (!routeItem) {
  33. let closeItem = this.closeAliveList.find((item: any) => item.fullPath == route.fullPath);
  34. routeItem = {
  35. fullPath: route.fullPath,
  36. times: 0
  37. };
  38. if (closeItem) {
  39. routeItem.times = closeItem.times + 1;
  40. this.closeAliveList = this.closeAliveList.filter((item: any) => item.fullPath != route.fullPath);
  41. }
  42. this.keepAliveList.push(routeItem);
  43. // 检查缓存数量限制,如果超过限制则清理最旧的缓存
  44. this.cleanOldKeepAliveCache();
  45. }
  46. },
  47. // Remove KeepAliveName
  48. removeKeepAliveName(route: any) {
  49. this.keepAliveName = this.keepAliveName.filter(item => item !== route.name);
  50. let index = this.keepAliveList.findIndex(item => item.fullPath === route.fullPath);
  51. if (index > -1) {
  52. if (this.closeAliveList.every(item => item.fullPath !== route.fullPath)) {
  53. this.closeAliveList.push(this.keepAliveList[index]);
  54. }
  55. this.keepAliveList.splice(index, 1);
  56. // this.keepAliveList[index].times = this.keepAliveList[index].times + 1;
  57. if (route.needLock && Number(route.billid)) {
  58. UnLockBill({
  59. keyword: String(route.keyword),
  60. billid: Number(route.billid),
  61. billcode: String(route.billcode)
  62. }).catch(err => {
  63. console.warn("解锁失败:", route.keyword, route.billid, err);
  64. });
  65. }
  66. }
  67. console.log("getKeepAliveName routeItem removeKeepAliveName :>> ", index, JSON.stringify(this.keepAliveList));
  68. console.log("getKeepAliveName routeItem removeKeepAliveName :>> ", this.keepAliveList);
  69. console.log("getKeepAliveName routeItem keepAliveName :>> ", this.keepAliveName);
  70. },
  71. // Set KeepAliveName
  72. async setKeepAliveName(keepAliveName: string[] = []) {
  73. this.keepAliveName = keepAliveName;
  74. },
  75. async updateKeepAliveName(route: any) {
  76. // this.addKeepAliveName(route);
  77. console.log("updateKeepAliveName route :>> ", route, this.keepAliveList);
  78. this.keepAliveList.forEach((item: any) => {
  79. console.log("updateKeepAliveName item :>> ", item.fullPath, route.fullPath, item.fullPath == route.fullPath);
  80. if (item.fullPath == route.fullPath) {
  81. item.times++;
  82. }
  83. });
  84. },
  85. getKeepAliveName(name: string) {
  86. let routeItem = this.keepAliveList.find((item: any) => item.fullPath == name);
  87. if (!routeItem) {
  88. // routeItem = {
  89. // fullPath: name,
  90. // times: 0
  91. // };
  92. // this.keepAliveList.push(routeItem);
  93. return undefined;
  94. }
  95. return routeItem;
  96. },
  97. // 清理过期的 keepAlive 缓存
  98. cleanOldKeepAliveCache() {
  99. if (this.keepAliveList.length <= MAX_KEEP_ALIVE_COUNT) {
  100. return;
  101. }
  102. const tabStore = useTabsStore();
  103. // 过滤出编辑页,跳过不清理
  104. const editPages = this.keepAliveList.filter(item => {
  105. const tab = tabStore.tabsMenuList.find(tabItem => tabItem.path === item.fullPath);
  106. return tab && (tab.path.includes("/edit?") || tab.path.includes("/edit/"));
  107. });
  108. // 计算可清理的缓存数量(总数减去编辑页数量)
  109. const availableToClean = this.keepAliveList.length - editPages.length;
  110. const maxCleanable = MAX_KEEP_ALIVE_COUNT - editPages.length;
  111. // 如果非编辑页数量在限制范围内,不需要清理
  112. if (availableToClean <= maxCleanable) {
  113. console.log(`非编辑页缓存数量 (${availableToClean}) 在限制范围内,跳过清理`);
  114. return;
  115. }
  116. console.log(
  117. `keepAlive 缓存数量 (${this.keepAliveList.length}) 超过限制,编辑页: ${editPages.length},开始清理非编辑页缓存`
  118. );
  119. // 获取非编辑页缓存进行清理
  120. const nonEditPages = this.keepAliveList.filter(item => {
  121. const tab = tabStore.tabsMenuList.find(tabItem => tabItem.path === item.fullPath);
  122. return !tab || (!tab.path.includes("/edit?") && !tab.path.includes("/edit/"));
  123. });
  124. // 按访问次数排序,优先清理访问次数少的缓存
  125. const sortedCache = nonEditPages.sort((a, b) => a.times - b.times);
  126. // 需要清理的数量
  127. const removeCount = availableToClean - maxCleanable;
  128. const toRemove = sortedCache.slice(0, removeCount);
  129. // 清理 keepAliveName 和 keepAliveList
  130. toRemove.forEach(item => {
  131. console.log(`清理 keepAlive 缓存: ${item.fullPath} (访问次数: ${item.times})`);
  132. // 从 keepAliveList 中移除
  133. const index = this.keepAliveList.findIndex(cacheItem => cacheItem.fullPath === item.fullPath);
  134. if (index > -1) {
  135. this.keepAliveList.splice(index, 1);
  136. }
  137. // 从 keepAliveName 中移除对应的组件名
  138. const tab = tabStore.tabsMenuList.find(tabItem => tabItem.path === item.fullPath);
  139. if (tab && tab.name) {
  140. const nameIndex = this.keepAliveName.indexOf(tab.name);
  141. if (nameIndex > -1) {
  142. this.keepAliveName.splice(nameIndex, 1);
  143. }
  144. }
  145. });
  146. console.log(`keepAlive 缓存清理完成,当前缓存数量: ${this.keepAliveList.length}`);
  147. },
  148. computedKeepAliveName(to, from) {
  149. this.computedKeepAliveNamePro(to, from);
  150. return;
  151. const tabStore = useTabsStore();
  152. const tabsParams = {
  153. icon: to.meta.icon as string,
  154. title: to.meta.title as string,
  155. path: to.fullPath,
  156. fullPath: to.fullPath,
  157. name: to.name as string,
  158. close: !to.meta.isAffix
  159. };
  160. if (to.meta.needLock && Number(to.query.id)) {
  161. tabsParams["needLock"] = to.meta.needLock;
  162. tabsParams["keyword"] = String(to.meta.billtype);
  163. tabsParams["billid"] = Number(to.query.id);
  164. tabsParams["billcode"] = String(to.query.code);
  165. }
  166. const isEditToDetail =
  167. from.fullPath.includes("/edit") && to.fullPath.includes("/detail") && from.query?.id && from.meta?.needLock;
  168. const isSameBill = from.query?.code === to.query?.code;
  169. if (isEditToDetail && isSameBill) {
  170. UnLockBill({
  171. keyword: String(from.meta.billtype),
  172. billid: Number(from.query.id),
  173. billcode: String(from.query.code)
  174. }).catch(err => {
  175. console.warn("edit->detail 解锁失败:", err);
  176. });
  177. }
  178. let isReplace = false;
  179. if (tabsParams.path.indexOf("/edit?") > -1 || tabsParams.path.indexOf("/edit/") > -1) {
  180. let key = "/edit?";
  181. let name = "";
  182. let _path = tabsParams.path.replace(key, "/detail?");
  183. // let _path = from.fullPath;
  184. // 使用路由参数的地址,忽略查询参数后,查找相同地址
  185. let _path2 = tabsParams.path.replace("/edit/", "/detail/");
  186. let hasPath1 = to.fullPath.indexOf("/edit?") > -1;
  187. let hasPath2 = to.fullPath.indexOf("/edit/") > -1;
  188. let tabsMenuList = tabStore.tabsMenuList;
  189. console.log("tabsMenuList :>> ", JSON.stringify(tabsMenuList));
  190. for (let i = 0; i < tabsMenuList.length; i++) {
  191. if (
  192. (hasPath1 && tabsMenuList[i].path == _path) ||
  193. (hasPath2 && tabsMenuList[i].path.split("?")[0] == _path2.split("?")[0])
  194. ) {
  195. name = tabsMenuList[i].name;
  196. tabsMenuList[i] = tabsParams;
  197. isReplace = true;
  198. break;
  199. }
  200. }
  201. console.log("keilll name edit:>> ", name);
  202. tabStore.setTabs(tabsMenuList);
  203. isReplace && this.addKeepAliveName(to);
  204. isReplace && this.removeKeepAliveName(from);
  205. name && to.meta.isKeepAlive && this.updateKeepAliveName(to);
  206. // name && this.updateKeepAliveName(to);
  207. // to.meta.isKeepAlive && this.updateKeepAliveName(route);
  208. } else if (tabsParams.path.indexOf("/detail?") > -1 || tabsParams.path.indexOf("/detail/") > -1) {
  209. let key = "/detail?";
  210. let name = "";
  211. let _path = tabsParams.path.replace(key, "/edit?");
  212. // let _path = from.fullPath;
  213. // 使用路由参数的地址,忽略查询参数后,查找相同地址
  214. let _path2 = tabsParams.path.replace("/detail/", "/edit/");
  215. let hasPath1 = to.fullPath.indexOf("/detail?") > -1;
  216. let hasPath2 = to.fullPath.indexOf("/detail/") > -1;
  217. let tabsMenuList = tabStore.tabsMenuList;
  218. console.log("tabsMenuList :>> ", JSON.stringify(tabsMenuList));
  219. for (let i = 0; i < tabsMenuList.length; i++) {
  220. if (
  221. (hasPath1 &&
  222. (tabsMenuList[i].path == _path ||
  223. (tabsMenuList[i].path == from.fullPath && from.fullPath.indexOf("/copy?") > -1))) ||
  224. (hasPath2 && tabsMenuList[i].path.split("?")[0] == _path2.split("?")[0])
  225. ) {
  226. name = tabsMenuList[i].name;
  227. tabsMenuList[i] = tabsParams;
  228. isReplace = true;
  229. break;
  230. }
  231. }
  232. tabStore.setTabs(tabsMenuList);
  233. console.log("keilll name detail:>> ", name);
  234. isReplace && this.addKeepAliveName(to);
  235. isReplace && this.removeKeepAliveName(from);
  236. name && to.meta.isKeepAlive && this.updateKeepAliveName(to);
  237. // name && this.updateKeepAliveName(to);
  238. // to.meta.isKeepAlive && this.addKeepAliveName(to);
  239. }
  240. // }
  241. !isReplace && tabStore.addTabs(tabsParams);
  242. !isReplace && to.meta.isKeepAlive && this.addKeepAliveName(to);
  243. },
  244. async computedKeepAliveNamePro(to, from) {
  245. const tabStore = useTabsStore();
  246. const tabsMenuList = [...tabStore.tabsMenuList];
  247. let isReplace = false;
  248. // --- 生成 tab 参数 ---
  249. const tabsParams = {
  250. icon: to.meta.icon as string,
  251. title: to.meta.title as string,
  252. path: to.fullPath,
  253. fullPath: to.fullPath,
  254. name: to.name as string,
  255. close: !to.meta.isAffix,
  256. query: to.query
  257. };
  258. if (to.meta.needLock && Number(to.query?.id)) {
  259. tabsParams["needLock"] = to.meta.needLock;
  260. tabsParams["keyword"] = String(to.meta.billtype);
  261. tabsParams["billid"] = Number(to.query.id);
  262. tabsParams["billcode"] = String(to.query.code);
  263. }
  264. // --- 工具函数 ---
  265. const getBillCode = route => route?.query?.code || "";
  266. const getBillType = route => {
  267. const pathMatch = route?.fullPath?.match(/\/([a-zA-Z]+)\/(detail|edit|copy|index)/);
  268. return pathMatch ? pathMatch[1] : "";
  269. }; // 改为 meta 管理类型更可靠
  270. const isSameBill = (code1, type1, code2, type2) =>
  271. !!code1 && !!code2 && code1 === code2 && !!type1 && !!type2 && type1 === type2;
  272. const toBillCode = getBillCode(to);
  273. const fromBillCode = getBillCode(from);
  274. const toBillType = getBillType(to);
  275. const fromBillType = getBillType(from);
  276. // --- 查找 tab 工具函数 ---
  277. const findTabIndex = (tabType: "edit" | "detail" | "copy") => {
  278. return tabsMenuList.findIndex(item => {
  279. const itemBillCode = getBillCode(item);
  280. const itemBillType = getBillType(item);
  281. const isTypeMatch =
  282. (tabType === "edit" && item.path.includes("/edit")) ||
  283. (tabType === "detail" && item.path.includes("/detail")) ||
  284. (tabType === "copy" && item.path.includes("/copy"));
  285. return isSameBill(itemBillCode, itemBillType, toBillCode, toBillType) && isTypeMatch;
  286. });
  287. };
  288. // --- 替换 tab 并管理 keep-alive ---
  289. const replaceTab = (index: number) => {
  290. const oldTab = tabsMenuList[index];
  291. tabsMenuList[index] = tabsParams;
  292. tabStore.setTabs(tabsMenuList);
  293. if (oldTab?.name) {
  294. this.removeKeepAliveName(oldTab); // 移除旧缓存
  295. }
  296. if (to.meta.isKeepAlive) {
  297. this.addKeepAliveName(to);
  298. this.updateKeepAliveName(to);
  299. }
  300. isReplace = true;
  301. };
  302. // --- 解锁单据 ---
  303. const unlockBill = async route => {
  304. if (route?.meta?.needLock && route?.query?.id) {
  305. try {
  306. await UnLockBill({
  307. keyword: String(route.meta.billtype),
  308. billid: Number(route.query.id),
  309. billcode: String(route.query.code)
  310. });
  311. } catch (err) {
  312. console.warn("解锁单据失败:", err);
  313. }
  314. }
  315. };
  316. // --- 逻辑判断 ---
  317. const fromName = from?.name || "";
  318. // 情况1: 列表 -> 详情
  319. if ((!from || fromNameIncludes(from, "/index")) && toNameIncludes(to, "/detail") && toBillCode) {
  320. const editTabIndex = findTabIndex("edit");
  321. if (editTabIndex > -1) {
  322. await unlockBill(tabsMenuList[editTabIndex]);
  323. replaceTab(editTabIndex);
  324. }
  325. }
  326. // 情况2: 详情 -> 编辑
  327. if (
  328. fromName &&
  329. toNameIncludes(to, "/edit") &&
  330. fromNameIncludes(from, "/detail") &&
  331. isSameBill(toBillCode, toBillType, fromBillCode, fromBillType)
  332. ) {
  333. const detailTabIndex = findTabIndex("detail");
  334. if (detailTabIndex > -1) replaceTab(detailTabIndex);
  335. }
  336. // 情况3: 编辑 -> 详情
  337. if (
  338. fromName &&
  339. fromNameIncludes(from, "/edit") &&
  340. toNameIncludes(to, "/detail") &&
  341. isSameBill(toBillCode, toBillType, fromBillCode, fromBillType)
  342. ) {
  343. const editTabIndex = findTabIndex("edit");
  344. if (editTabIndex > -1) {
  345. await unlockBill(from); // 保证顺序
  346. replaceTab(editTabIndex);
  347. }
  348. }
  349. // 情况4: 列表 -> 编辑
  350. if ((!from || fromNameIncludes(from, "/index")) && toNameIncludes(to, "/edit") && toBillCode) {
  351. const detailTabIndex = findTabIndex("detail");
  352. if (detailTabIndex > -1) replaceTab(detailTabIndex);
  353. }
  354. // 情况5: 复制 -> 详情
  355. if (fromNameIncludes(from, "/copy") && toNameIncludes(to, "/detail") && to.query?.fromCopySave) {
  356. const copyTabIndex = tabsMenuList.findIndex(item => item.path === from.fullPath);
  357. if (copyTabIndex > -1) replaceTab(copyTabIndex);
  358. }
  359. // 默认添加 tab
  360. if (!isReplace) {
  361. tabStore.addTabs(tabsParams);
  362. if (to.meta.isKeepAlive) {
  363. this.addKeepAliveName(to);
  364. }
  365. }
  366. // --- 辅助函数: 判断 route.name 类型 ---
  367. function toNameIncludes(route, type: string) {
  368. return route?.fullPath?.toString().toLowerCase().includes(type);
  369. }
  370. function fromNameIncludes(route, type: string) {
  371. return route?.fullPath?.toString().toLowerCase().includes(type);
  372. }
  373. }
  374. }
  375. });