App.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. <template>
  2. <el-config-provider :size="getGlobalComponentSize" :locale="zhCn" :message="messageConfig" :button="buttonConfig">
  3. <router-view v-show="setLockScreen" />
  4. <LockScreen v-if="themeConfig.isLockScreen" />
  5. <SetTings ref="setTingsRef" v-show="setLockScreen" />
  6. <CloseFull v-if="!themeConfig.isLockScreen" />
  7. </el-config-provider>
  8. </template>
  9. <script lang="tsx" name="app" setup>
  10. import { computed, ref, onBeforeMount, onMounted, onBeforeUnmount, nextTick, watch, reactive, defineAsyncComponent } from 'vue';
  11. import { useRoute, useRouter } from 'vue-router';
  12. import zhCn from 'element-plus/es/locale/lang/zh-cn';
  13. import { storeToRefs } from 'pinia';
  14. import { useTagsViewRoutes } from '@/stores/tagsViewRoutes';
  15. import { useThemeConfig } from '@/stores/themeConfig';
  16. import other from '@/utils/other';
  17. import checkUpdate from '@/utils/checkUpdate';
  18. import mittBus from '@/utils/mitt';
  19. import { Session, Local, Cookie } from '@/utils/storage';
  20. import setIntroduction from '@/utils/setIconfont';
  21. import { loginPageInfo } from '@/api/login';
  22. import { downloadFileByStream, getImageUrl } from '@/utils/tools';
  23. import { useKeepALiveNames } from '@/stores/keepAliveNames';
  24. import { useFavicon, useDark } from '@vueuse/core';
  25. import { NextLoading } from '@/utils/loading';
  26. import { ElMessageBox, ElNotification } from 'element-plus';
  27. import { formatAxis, formatDate } from '@/utils/formatTime';
  28. import { initFrontEndControlRoutes } from '@/router/frontEnd';
  29. import { initBackEndControlRoutes } from '@/router/backEnd';
  30. import { VxeUI, VxeButton } from 'vxe-pc-ui';
  31. import MenuSvgIcon from '@/views/system/menu/components/Menu-svgIcon.vue';
  32. import { getTokenByUrl } from '@/api/home';
  33. // 引入组件
  34. const LockScreen = defineAsyncComponent(() => import('@/layout/lockScreen/index.vue'));
  35. const SetTings = defineAsyncComponent(() => import('@/layout/navBars/breadcrumb/setings.vue'));
  36. const CloseFull = defineAsyncComponent(() => import('@/layout/navBars/breadcrumb/closeFull.vue'));
  37. const route = useRoute();
  38. const stores = useTagsViewRoutes();
  39. const storesThemeConfig = useThemeConfig();
  40. const { themeConfig } = storeToRefs(storesThemeConfig);
  41. const storesKeepALiveNames = useKeepALiveNames();
  42. const storesTagsViewRoutes = useTagsViewRoutes();
  43. const { tagsViewRoutes } = storeToRefs(storesTagsViewRoutes);
  44. // 式化日期,默认 yyyy-MM-dd HH:mm:ss
  45. VxeUI.formats.add('formatDate', {
  46. tableCellFormatMethod({ cellValue }, format?: string) {
  47. return formatDate(cellValue, format || 'YYYY-mm-dd HH:MM:SS');
  48. },
  49. });
  50. const exportTips = ref('导出已留痕,禁止泄露工单信息,否则将依法依规追究责任。您确定导出当前页数据?');
  51. const exportAllTips = ref('导出已留痕,禁止泄露工单信息,否则将依法依规追究责任。您确定要导出全部数据?');
  52. // 判断地市
  53. /*if (themeConfig.value.appScope === 'YiBin') {
  54. exportTips.value = '您确定导出当前页数据?';
  55. exportAllTips.value = '您确定要导出全部数据?';
  56. } else if (themeConfig.value.appScope === 'ZiGong') {
  57. exportTips.value = '您确定导出当前页数据?';
  58. exportAllTips.value = '您确定要导出全部数据?';
  59. } else if (themeConfig.value.appScope === 'LuZhou') {
  60. exportTips.value = '导出已留痕,禁止泄露工单信息,否则将依法依规追究责任。您确定导出当前页数据?';
  61. exportAllTips.value = '导出已留痕,禁止泄露工单信息,否则将依法依规追究责任。您确定要导出全部数据?';
  62. }*/
  63. // 创建一个简单的工具栏-右侧工具渲染
  64. VxeUI.renderer.add('exportCurrent', {
  65. renderToolbarTool(renderOpts: any, params: any) {
  66. return (
  67. <VxeButton
  68. title="导出当前页"
  69. circle
  70. onClick={() => {
  71. const { $table } = params;
  72. const columns = $table.getColumns();
  73. const tableParams = $table.getParams();
  74. if (!tableParams) {
  75. VxeUI.modal.message({
  76. content: `参数错误请检查`,
  77. status: 'warning',
  78. });
  79. return;
  80. }
  81. const exportNewColumns = columns
  82. .map((item: any) => {
  83. return {
  84. prop: item.field,
  85. name: item.title,
  86. type: item.type,
  87. };
  88. })
  89. .filter((item: any) => item.prop && item.name && !['seq', 'checkbox'].includes(item.type));
  90. ElMessageBox.confirm(`${exportTips.value}`, '提示', {
  91. confirmButtonText: '确认',
  92. cancelButtonText: '取消',
  93. type: 'warning',
  94. draggable: true,
  95. cancelButtonClass: 'default-button',
  96. autofocus: false,
  97. })
  98. .then(() => {
  99. VxeUI.modal.message({
  100. content: `导出中,请稍等`,
  101. status: 'loading',
  102. id: 'exportCurrent',
  103. duration: -1,
  104. });
  105. const request = {
  106. queryDto: tableParams.exportParams,
  107. columnInfos: exportNewColumns,
  108. isExportAll: false,
  109. };
  110. tableParams.exportMethod &&
  111. tableParams
  112. .exportMethod(request)
  113. .then((res: any) => {
  114. downloadFileByStream(res);
  115. VxeUI.modal.close('exportCurrent');
  116. VxeUI.modal.message({
  117. content: `导出成功`,
  118. status: 'success',
  119. });
  120. })
  121. .catch((e: any) => {
  122. console.log(`导出失败:${e}`);
  123. VxeUI.modal.close('exportCurrent');
  124. VxeUI.modal.message({
  125. content: `导出失败`,
  126. status: 'error',
  127. });
  128. });
  129. })
  130. .catch(() => {});
  131. }}
  132. >
  133. <MenuSvgIcon name="iconfont icon-daochu" />
  134. </VxeButton>
  135. );
  136. },
  137. });
  138. VxeUI.renderer.add('exportAll', {
  139. renderToolbarTool(renderOpts: any, params: any) {
  140. return (
  141. <VxeButton
  142. className="mr10"
  143. title="导出全部"
  144. circle
  145. onClick={() => {
  146. const { $table } = params;
  147. const columns = $table.getColumns();
  148. const tableParams = $table.getParams();
  149. if (!tableParams) {
  150. VxeUI.modal.message({
  151. content: `参数错误请检查`,
  152. status: 'warning',
  153. });
  154. return;
  155. }
  156. let request = {};
  157. if (tableParams.isSpecialExport) {
  158. // 特殊导出请求参数
  159. const exportNewColumns = columns.filter((item: any) => item.field && item.title && !['seq', 'checkbox'].includes(item.type));
  160. const addColumnName = exportNewColumns.map((item: any) => item.title);
  161. request = {
  162. ...tableParams.exportParams,
  163. addColumnName,
  164. };
  165. } else {
  166. const exportNewColumns = columns
  167. .map((item: any) => {
  168. return {
  169. prop: item.field,
  170. name: item.title,
  171. type: item.type,
  172. };
  173. })
  174. .filter((item: any) => item.prop && item.name && !['seq', 'checkbox'].includes(item.type));
  175. request = {
  176. queryDto: tableParams.exportParams,
  177. columnInfos: exportNewColumns,
  178. isExportAll: true,
  179. };
  180. }
  181. ElMessageBox.confirm(`${exportAllTips.value}`, '提示', {
  182. confirmButtonText: '确认',
  183. cancelButtonText: '取消',
  184. type: 'warning',
  185. draggable: true,
  186. cancelButtonClass: 'default-button',
  187. autofocus: false,
  188. })
  189. .then(() => {
  190. VxeUI.modal.message({
  191. content: `导出中,请稍等`,
  192. status: 'loading',
  193. id: 'exportAll',
  194. duration: -1,
  195. });
  196. tableParams.exportMethod &&
  197. tableParams
  198. .exportMethod(request)
  199. .then((res: any) => {
  200. downloadFileByStream(res);
  201. VxeUI.modal.close('exportAll');
  202. VxeUI.modal.message({
  203. content: `导出成功`,
  204. status: 'success',
  205. });
  206. })
  207. .catch((e: any) => {
  208. console.log(`导出失败:${e}`);
  209. VxeUI.modal.close('exportAll');
  210. VxeUI.modal.message({
  211. content: `导出失败`,
  212. status: 'error',
  213. });
  214. });
  215. })
  216. .catch(() => {});
  217. }}
  218. >
  219. <MenuSvgIcon name="iconfont icon-export" />
  220. </VxeButton>
  221. );
  222. },
  223. });
  224. // 设置锁屏时组件显示隐藏
  225. const setLockScreen = computed(() => {
  226. // 防止锁屏后,刷新出现不相关界面
  227. // https://gitee.com/lyt-top/vue-next-admin/issues/I6AF8P
  228. return !themeConfig.value.isLockScreen;
  229. });
  230. // 可同时显示的消息最大数量
  231. const messageConfig = {
  232. max: 3, // 最大数量
  233. offset: 70, // 距离顶部的距离
  234. };
  235. // 自动在两个中文字符之间插入空格
  236. const buttonConfig = {
  237. autoInsertSpace: false,
  238. };
  239. // 获取全局组件大小
  240. const getGlobalComponentSize = computed(() => {
  241. return other.globalComponentSize();
  242. });
  243. // 布局配置弹窗打开
  244. const setTingsRef = ref<RefType>();
  245. const openSetTingsDrawer = () => {
  246. setTingsRef.value.openDrawer();
  247. };
  248. // 设置初始化,防止刷新时恢复默认
  249. onBeforeMount(async () => {
  250. try {
  251. // 获取登录页的背景图和系统名称等
  252. const { result } = await loginPageInfo();
  253. const globalTitle = result.sysName ?? ''; // 标题名称
  254. const loginImage = result.loginImage ? result.loginImage : `${getImageUrl('default/login_bg.png')}`; // 登录页背景图
  255. const isLoginMessageCode = result.isLoginMessageCode; // 是否开启短信验证码
  256. const appScope = result.appScope ?? 'YiBin'; // 应用范围
  257. const cityName = result.cityName ?? ''; // 城市名称
  258. const cityCode = result.cityCode ?? ''; // 城市编码
  259. const cityAbbr = result.cityAbbr ?? ''; // 城市简称
  260. const operate = result.operate ?? ''; // 管理运营 页脚
  261. const techSupport = result.techSupport ?? ''; // 技术支持 页脚
  262. const recordNumber = result.recordNumber ?? ''; // 备案号 页脚
  263. const faviconImage = result.faviconImage ?? ``; // favicon 浏览器标签图标
  264. const menuLogoImage = result.menuLogoImage ?? ``; // 菜单logo
  265. const menuLogoImageMini = result.menuLogoImageMini ?? ``; // 菜单logo-mini
  266. const changPwdImage = result.changPwdImage ?? ``; // 修改密码图片
  267. storesThemeConfig.setThemeConfig(
  268. Object.assign(themeConfig.value, {
  269. globalTitle,
  270. loginImage,
  271. isLoginMessageCode,
  272. appScope,
  273. cityName,
  274. cityCode,
  275. cityAbbr,
  276. operate,
  277. techSupport,
  278. recordNumber,
  279. faviconImage,
  280. menuLogoImage,
  281. menuLogoImageMini,
  282. changPwdImage,
  283. })
  284. );
  285. } catch (e) {
  286. console.log(e);
  287. }
  288. // 设置批量第三方 icon 图标
  289. setIntroduction.cssCdn();
  290. // 设置批量第三方 js
  291. setIntroduction.jsCdn();
  292. });
  293. const router = useRouter();
  294. // 登录成功后的跳转
  295. const signInSuccess = (isNoPower: boolean | undefined) => {
  296. window.history.replaceState({}, document.title, window.location.pathname);
  297. if (isNoPower) {
  298. ElNotification({
  299. title: '提示',
  300. message: '抱歉,您没有登录权限,请联系管理员',
  301. type: 'warning',
  302. });
  303. Session.clear();
  304. Cookie.clear();
  305. Local.clear();
  306. setTimeout(() => {
  307. window.location.reload();
  308. }, 2000);
  309. } else {
  310. router.push('/');
  311. // 设置登录成功后的时间问候语
  312. const signInText = '欢迎回来!';
  313. ElNotification({
  314. type: 'success',
  315. title: `${formatAxis(new Date())}`,
  316. message: `${signInText}`,
  317. position: 'bottom-right',
  318. });
  319. NextLoading.start();
  320. }
  321. };
  322. // 页面加载时
  323. onMounted(() => {
  324. nextTick(async () => {
  325. try {
  326. // 获取缓存中的布局配置
  327. if (Local.get('themeConfig')) {
  328. storesThemeConfig.setThemeConfig(Local.get('themeConfig'));
  329. document.documentElement.style.cssText = Local.get('themeConfigStyle');
  330. }
  331. // 开发环境不提示更新
  332. if (import.meta.env.VITE_MODE_NAME != 'development') {
  333. // 监听是否更新 半个小时检查一次
  334. await checkUpdate(1800);
  335. }
  336. // 监听布局配置弹窗点击打开
  337. mittBus.on('openSetTingsDrawer', () => {
  338. openSetTingsDrawer();
  339. });
  340. // 获取缓存中的全屏配置
  341. if (Session.get('isTagsViewCurrenFull')) {
  342. stores.setCurrenFullscreen(Session.get('isTagsViewCurrenFull'));
  343. }
  344. // 清除某个页面的缓存
  345. mittBus.on('clearCache', (view: any) => {
  346. clearCacheTagsView(view);
  347. });
  348. // 解决火狐拖动打开新窗口
  349. document.body.ondrop = (event) => {
  350. event.preventDefault();
  351. event.stopPropagation();
  352. };
  353. // 动态修改icon
  354. const icon = useFavicon();
  355. icon.value = themeConfig.value.faviconImage; // 更改当前左上角角标
  356. const isDark = useDark();
  357. isDark.value = themeConfig.value.isIsDark; // 更改暗黑模式
  358. /*mittBus.on('*', (index, data) => {
  359. console.log(index, data);
  360. });*/
  361. /* // 通过url判断单点登录
  362. // 获取url特殊参数 实现登录跳转
  363. // 单点登录进来的参数
  364. const urlParams = new URLSearchParams(window.location.search);
  365. const source = urlParams.get('source');
  366. const username = urlParams.get('username');
  367. // 判断是否是单点登录
  368. const isSingleSignOn = source === 'oldHotline' && username;
  369. if (isSingleSignOn) {
  370. Cookie.remove('token');
  371. getTokenByUrl({ userName: username })
  372. .then(async (res: any) => {
  373. //登录
  374. // 存储 token 到浏览器缓存
  375. Cookie.set('token', res.result);
  376. window.history.replaceState({}, document.title, window.location.pathname);
  377. if (!themeConfig.value.isRequestRoutes) {
  378. // 前端控制路由,2、请注意执行顺序
  379. const isNoPower = await initFrontEndControlRoutes();
  380. signInSuccess(isNoPower);
  381. } else {
  382. // 模拟后端控制路由,isRequestRoutes 为 true,则开启后端控制路由
  383. // 添加完动态路由,再进行 router 跳转,否则可能报错 No match found for location with path "/"
  384. const isNoPower = await initBackEndControlRoutes();
  385. // 执行完 initBackEndControlRoutes,再执行 signInSuccess
  386. signInSuccess(isNoPower);
  387. }
  388. })
  389. .catch((err) => {
  390. const message = err.response.data.message;
  391. ElMessageBox.alert(message, '登录失败', {
  392. confirmButtonText: '确定',
  393. type: 'error',
  394. showClose: false,
  395. draggable: true,
  396. callback: () => {
  397. window.history.replaceState({}, document.title, window.location.pathname);
  398. // 清除缓存/token等
  399. Local.clear();
  400. Session.clear();
  401. Cookie.clear();
  402. // 使用 reload 时,不需要调用 resetRoute() 重置路由
  403. window.location.reload();
  404. },
  405. });
  406. });
  407. }*/
  408. // 通过url判断单点登录
  409. // 获取url特殊参数 实现登录跳转
  410. // 单点登录进来的参数
  411. const urlParams = new URLSearchParams(window.location.search);
  412. const source = urlParams.get('token');
  413. // 判断是否是单点登录
  414. if (source) {
  415. Cookie.remove('token');
  416. //登录
  417. // 存储 token 到浏览器缓存
  418. Cookie.set('token', source);
  419. if (!themeConfig.value.isRequestRoutes) {
  420. // 前端控制路由,2、请注意执行顺序
  421. const isNoPower = await initFrontEndControlRoutes();
  422. signInSuccess(isNoPower);
  423. } else {
  424. // 模拟后端控制路由,isRequestRoutes 为 true,则开启后端控制路由
  425. // 添加完动态路由,再进行 router 跳转,否则可能报错 No match found for location with path "/"
  426. const isNoPower = await initBackEndControlRoutes();
  427. // 执行完 initBackEndControlRoutes,再执行 signInSuccess
  428. signInSuccess(isNoPower);
  429. }
  430. }
  431. } catch (error) {
  432. console.log(error);
  433. }
  434. });
  435. });
  436. // 清除缓存 name
  437. const clearCacheTagsView = async (routeName: string) => {
  438. let item: EmptyObjectType | null | undefined;
  439. tagsViewRoutes.value.forEach((v: any) => {
  440. if (v.name === routeName) {
  441. item = v;
  442. }
  443. });
  444. if (!item) return false;
  445. await storesKeepALiveNames.delCachedView(item);
  446. if (item.meta?.isKeepAlive) await storesKeepALiveNames.addCachedView(item);
  447. };
  448. // 页面销毁时,关闭监听布局配置/i18n监听
  449. onBeforeUnmount(() => {
  450. mittBus.off('openSetTingsDrawer', () => {});
  451. mittBus.off('clearCache', () => {});
  452. });
  453. // 监听路由的变化,设置网站标题
  454. watch(
  455. () => route.path,
  456. () => {
  457. other.useTitle();
  458. },
  459. {
  460. deep: true,
  461. }
  462. );
  463. </script>