theme.js 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. /**
  2. * 主题 / 页签状态(精简版,对齐 WT 布局所需能力)
  3. */
  4. import {
  5. screenWidth,
  6. screenHeight,
  7. contentWidth,
  8. contentHeight,
  9. } from 'ele-admin';
  10. import {
  11. TAB_KEEP_ALIVE,
  12. KEEP_ALIVE_EXCLUDES,
  13. getThemeStoreName,
  14. HOME_PATH,
  15. LAYOUT_PATH,
  16. isHomeAliasPath,
  17. } from '@/config/setting';
  18. import Vue from 'vue';
  19. const HOME_ROUTE = HOME_PATH || '/home/index';
  20. function isHomeTabKey(key) {
  21. if (!key) return false;
  22. if (key === LAYOUT_PATH || key === '/') return true;
  23. const pathOnly = String(key).split('?')[0];
  24. return isHomeAliasPath(pathOnly) || pathOnly === HOME_ROUTE;
  25. }
  26. function withoutHomeTabs(tabs = []) {
  27. return (tabs || []).filter(
  28. (tab) =>
  29. tab &&
  30. !isHomeTabKey(tab.key) &&
  31. !isHomeTabKey(tab.path) &&
  32. !isHomeTabKey(tab.fullPath),
  33. );
  34. }
  35. const DEFAULT_STATE = Object.freeze({
  36. tabs: [],
  37. collapse: false,
  38. sideNavCollapse: false,
  39. bodyFullscreen: false,
  40. showTabs: true,
  41. showFooter: false,
  42. headStyle: 'light',
  43. sideStyle: 'dark',
  44. layoutStyle: 'side',
  45. sideMenuStyle: 'default',
  46. tabStyle: 'card',
  47. transitionName: 'slide-right',
  48. fixedHeader: false,
  49. fixedSidebar: true,
  50. fixedBody: true,
  51. bodyFull: true,
  52. logoAutoSize: false,
  53. colorfulIcon: false,
  54. sideUniqueOpen: true,
  55. weakMode: false,
  56. darkMode: false,
  57. color: null,
  58. homeComponents: [],
  59. routeReload: null,
  60. screenWidth: screenWidth(),
  61. screenHeight: screenHeight(),
  62. contentWidth: contentWidth(),
  63. contentHeight: contentHeight(),
  64. styleResponsive: true,
  65. });
  66. function getCacheSetting() {
  67. try {
  68. const value = localStorage.getItem(getThemeStoreName());
  69. if (value) {
  70. const cache = JSON.parse(value);
  71. if (typeof cache === 'object' && cache !== null) return cache;
  72. }
  73. } catch (e) {
  74. console.error(e);
  75. }
  76. return {};
  77. }
  78. function cacheSetting(key, value) {
  79. const cache = getCacheSetting();
  80. if (cache[key] !== value) {
  81. cache[key] = value;
  82. localStorage.setItem(getThemeStoreName(), JSON.stringify(cache));
  83. }
  84. }
  85. export default {
  86. namespaced: true,
  87. state: (() => {
  88. const state = { ...DEFAULT_STATE };
  89. const cache = getCacheSetting();
  90. Object.keys(state).forEach((key) => {
  91. // 不从本地恢复页签:刷新后容易残留旧路径面包屑(如资质证照/新增)
  92. if (key === 'tabs' || key === 'homeComponents' || key === 'routeReload') {
  93. return;
  94. }
  95. if (typeof cache[key] !== 'undefined') state[key] = cache[key];
  96. });
  97. state.tabs = [];
  98. return state;
  99. })(),
  100. getters: {
  101. keepAliveInclude(state) {
  102. if (!TAB_KEEP_ALIVE || !state.showTabs) return [];
  103. const components = new Set();
  104. const { reloadPath } = state.routeReload || {};
  105. state.tabs?.forEach((t) => {
  106. const isAlive = t.meta?.keepAlive !== false;
  107. const isExclude = KEEP_ALIVE_EXCLUDES.includes(t.path);
  108. const isReload = reloadPath && reloadPath === t.fullPath;
  109. if (isAlive && !isExclude && !isReload && t.components) {
  110. t.components.forEach((c) => {
  111. if (typeof c === 'string' && c) components.add(c);
  112. });
  113. }
  114. });
  115. state.homeComponents?.forEach((c) => {
  116. if (typeof c === 'string' && c) {
  117. if (!state.routeReload?.reloadHome) components.add(c);
  118. }
  119. });
  120. return Array.from(components);
  121. },
  122. },
  123. mutations: {
  124. SET(state, { key, value }) {
  125. state[key] = value;
  126. },
  127. },
  128. actions: {
  129. setCollapse({ commit }, value) {
  130. commit('SET', { key: 'collapse', value });
  131. cacheSetting('collapse', value);
  132. },
  133. setSideNavCollapse({ commit }, value) {
  134. commit('SET', { key: 'sideNavCollapse', value });
  135. cacheSetting('sideNavCollapse', value);
  136. },
  137. setBodyFullscreen({ commit }, value) {
  138. commit('SET', { key: 'bodyFullscreen', value });
  139. cacheSetting('bodyFullscreen', value);
  140. },
  141. setTabs({ commit }, value) {
  142. commit('SET', { key: 'tabs', value: withoutHomeTabs(value) });
  143. },
  144. tabAdd({ state, commit }, data) {
  145. if (!data) return;
  146. if (
  147. isHomeTabKey(data.key) ||
  148. isHomeTabKey(data.path) ||
  149. isHomeTabKey(data.fullPath)
  150. ) {
  151. return;
  152. }
  153. const tabs = withoutHomeTabs(state.tabs || []);
  154. const index = tabs.findIndex((t) => t.key === data.key);
  155. if (index === -1) {
  156. commit('SET', { key: 'tabs', value: [...tabs, data] });
  157. } else {
  158. const next = tabs.slice();
  159. next[index] = { ...next[index], ...data };
  160. commit('SET', { key: 'tabs', value: next });
  161. }
  162. },
  163. tabRemove({ state, commit }, { key, active }) {
  164. // 与 EOM 一致:乾坤下页签由门户管理
  165. if (window.__POWERED_BY_QIANKUN__) {
  166. return Vue.prototype.$portalStore?.dispatch('theme/tabRemove', {
  167. key,
  168. active,
  169. });
  170. }
  171. if (isHomeTabKey(key)) return Promise.reject();
  172. const tabs = withoutHomeTabs(state.tabs || []);
  173. const index = tabs.findIndex((t) => t.key === key);
  174. if (index === -1) return Promise.reject();
  175. const target = tabs[index];
  176. if (target?.closable === false) return Promise.reject();
  177. const next = tabs.filter((t) => t.key !== key);
  178. commit('SET', { key: 'tabs', value: next });
  179. if (active === key) {
  180. const fallback = next[index] || next[index - 1];
  181. return Promise.resolve({
  182. path: fallback?.fullPath || fallback?.key,
  183. home: !fallback,
  184. });
  185. }
  186. return Promise.resolve({});
  187. },
  188. tabRemoveLeft({ state, commit }, { key, active }) {
  189. const tabs = withoutHomeTabs(state.tabs || []);
  190. const index = tabs.findIndex((t) => t.key === key);
  191. if (index <= 0) return Promise.reject();
  192. const next = tabs.slice(index);
  193. commit('SET', { key: 'tabs', value: next });
  194. if (!next.some((t) => t.key === active)) {
  195. return Promise.resolve({ path: next[0]?.fullPath || next[0]?.key });
  196. }
  197. return Promise.resolve({});
  198. },
  199. tabRemoveRight({ state, commit }, { key, active }) {
  200. const tabs = withoutHomeTabs(state.tabs || []);
  201. const index = tabs.findIndex((t) => t.key === key);
  202. if (index === -1 || index >= tabs.length - 1) return Promise.reject();
  203. const next = tabs.slice(0, index + 1);
  204. commit('SET', { key: 'tabs', value: next });
  205. if (!next.some((t) => t.key === active)) {
  206. return Promise.resolve({
  207. path: next[index]?.fullPath || next[index]?.key,
  208. home: !next[index],
  209. });
  210. }
  211. return Promise.resolve({});
  212. },
  213. tabRemoveOther({ state, commit }, { key }) {
  214. const tabs = withoutHomeTabs(state.tabs || []);
  215. if (isHomeTabKey(key)) {
  216. commit('SET', { key: 'tabs', value: [] });
  217. return Promise.resolve({ home: true });
  218. }
  219. const next = tabs.filter((t) => t.key === key);
  220. if (!next.length) return Promise.reject();
  221. commit('SET', { key: 'tabs', value: next });
  222. return Promise.resolve({
  223. path: next[0]?.fullPath || next[0]?.key,
  224. home: false,
  225. });
  226. },
  227. tabRemoveAll({ commit }) {
  228. commit('SET', { key: 'tabs', value: [] });
  229. cacheSetting('tabs', []);
  230. return Promise.resolve({ home: true });
  231. },
  232. tabSetItem({ state, commit }, data) {
  233. if (!data?.key || isHomeTabKey(data.key)) return;
  234. const tabs = withoutHomeTabs(state.tabs || []);
  235. const index = tabs.findIndex((t) => t.key === data.key);
  236. if (index === -1) return;
  237. const next = tabs.slice();
  238. next[index] = { ...next[index], ...data };
  239. commit('SET', { key: 'tabs', value: next });
  240. },
  241. setHomeComponents({ commit }, value) {
  242. commit('SET', { key: 'homeComponents', value: value || [] });
  243. },
  244. setRouteReload({ commit }, value) {
  245. commit('SET', { key: 'routeReload', value });
  246. return Promise.resolve();
  247. },
  248. updateScreenSize({ commit }) {
  249. commit('SET', { key: 'screenWidth', value: screenWidth() });
  250. commit('SET', { key: 'screenHeight', value: screenHeight() });
  251. commit('SET', { key: 'contentWidth', value: contentWidth() });
  252. commit('SET', { key: 'contentHeight', value: contentHeight() });
  253. },
  254. setColor({ commit }, value) {
  255. commit('SET', { key: 'color', value });
  256. cacheSetting('color', value);
  257. },
  258. setWeakMode({ commit }, value) {
  259. commit('SET', { key: 'weakMode', value });
  260. cacheSetting('weakMode', value);
  261. },
  262. setStyleResponsive({ commit }, value) {
  263. commit('SET', { key: 'styleResponsive', value });
  264. cacheSetting('styleResponsive', value);
  265. },
  266. recoverTheme({ commit, state }) {
  267. // 清理与当前地址无关的脏页签,避免面包屑残留「资质证照/新增」等旧路径
  268. const cache = getCacheSetting();
  269. const tabs = withoutHomeTabs(cache.tabs || state.tabs || []);
  270. commit('SET', { key: 'tabs', value: tabs });
  271. cacheSetting('tabs', tabs);
  272. },
  273. },
  274. };