ソースを参照

feat: 静默登录调整

zhangjie 6 ヶ月 前
コミット
60878575c0

+ 105 - 81
electron/main/index.ts

@@ -1,4 +1,4 @@
-import { app, shell, BrowserWindow, protocol, net } from 'electron';
+import { app, shell, BrowserWindow, protocol, net, ipcMain } from 'electron';
 import { join, resolve } from 'path';
 import url from 'url';
 import { electronApp, optimizer, is } from '@electron-toolkit/utils';
@@ -7,98 +7,122 @@ import useElectron from './useElectron';
 import useWinProcess from './useWinProcess';
 import log from './logger';
 
-// 免登录流程
-function silenceAuthorityHandle(mainWindow: BrowserWindow) {
-  const arg = process.argv;
-  log.info(`process.argv:${JSON.stringify(arg)}`);
-  if (!arg[1].startsWith('trd://')) return;
-  mainWindow.webContents.send('silence-authority', arg[1]);
-  // mainWindow.webContents.send(
-  //   'silence-authority',
-  //   'zgiOtrHS2nq9q3SOr0iOYAW1JJsG2w1lrqdv0PB2IrT9ijPBsKxT7I7u2077RaKAPbwfEj+9f/zr+RHzOZFsaYZo5GuoJLMdJPKvvOnpG/3jk6GsdPFlMooCjs5LzJKutFiFOj/xsXoOGdxqIYXWWH6sROZQAKhvBIu2Mfem6+fZhy66baK/s0ihec59gOTQ8lsjyJzf8To2K0EyapRWGVpFwljAmaQ1HfhfUUoRIYTIHW89wqRU3KYjB5oJQRQnVYLA7v0H9kK0smmX3N3Kbc0V9LtHUIiU/teGvWaa6ty43SAJqci9IRZEh35xzM9IjtzvR/9LHQWZqTLuVSZ+JmCwfKKGFV39ewYjrjmFk34tIW5yvmQ9oUugpi2T3xhdMA7aeNkkoK0tgm3hwqri3KdziGcqdEdkqhfW2vapwPDy4m5g/JH7ulNLI0nfDmPpTNsWgWXjUnmAQNWAp4In1oH8TMRNHImMbsxz4RhcBz/z/YCWl89zk/vA4TOtQDV99s66+8kVhTQEjDXffz6z2T5hZTGaPMv/lSCo1Fo01VM='
-  // );
-}
+const ULR_SCHEME_PROTOCOL = 'trd://';
+let mainWindow: BrowserWindow | null = null;
 
-function createWindow(): void {
-  // Create the browser window.
-  const mainWindow = new BrowserWindow({
-    width: is.dev ? 1428 : 1024,
-    height: 700,
-    minWidth: 1024,
-    minHeight: 600,
-    show: false,
-    ...(process.platform === 'linux' ? { icon } : {}),
-    webPreferences: {
-      preload: join(__dirname, '../preload/index.js'),
-      sandbox: false,
-      webSecurity: false,
-    },
-  });
+function main() {
+  const gotTheLock = app.requestSingleInstanceLock();
+  log.info(`gotTheLock:${gotTheLock}`);
+  if (!gotTheLock) {
+    app.quit();
+    return;
+  }
 
-  mainWindow.on('ready-to-show', () => {
-    mainWindow.show();
-    silenceAuthorityHandle(mainWindow);
-  });
+  function createWindow(): void {
+    // Create the browser window.
+    mainWindow = new BrowserWindow({
+      width: is.dev ? 1428 : 1024,
+      height: 700,
+      minWidth: 1024,
+      minHeight: 600,
+      show: false,
+      ...(process.platform === 'linux' ? { icon } : {}),
+      webPreferences: {
+        preload: join(__dirname, '../preload/index.js'),
+        sandbox: false,
+        webSecurity: false,
+      },
+    });
+    if (!mainWindow) return;
 
-  mainWindow.webContents.setWindowOpenHandler((details) => {
-    shell.openExternal(details.url);
-    return { action: 'deny' };
-  });
+    mainWindow.on('ready-to-show', () => {
+      mainWindow?.show();
+    });
 
-  // HMR for renderer base on electron-vite cli.
-  // Load the remote URL for development or the local html file for production.
-  if (is.dev && process.env.ELECTRON_RENDERER_URL) {
-    mainWindow.loadURL(process.env.ELECTRON_RENDERER_URL);
-    mainWindow.webContents.openDevTools();
-  } else {
-    mainWindow.loadFile(join(__dirname, '../renderer/index.html'));
+    mainWindow.webContents.setWindowOpenHandler((details) => {
+      shell.openExternal(details.url);
+      return { action: 'deny' };
+    });
+
+    // HMR for renderer base on electron-vite cli.
+    // Load the remote URL for development or the local html file for production.
+    if (is.dev && process.env.ELECTRON_RENDERER_URL) {
+      mainWindow.loadURL(process.env.ELECTRON_RENDERER_URL);
+      mainWindow.webContents.openDevTools();
+    } else {
+      mainWindow.loadFile(join(__dirname, '../renderer/index.html'));
+    }
   }
-}
 
-// This method will be called when Electron has finished
-// initialization and is ready to create browser windows.
-// Some APIs can only be used after this event occurs.
-app.whenReady().then(() => {
-  // Set app user model id for windows
-  electronApp.setAppUserModelId('com.electron');
-
-  // Default open or close DevTools by F12 in development
-  // and ignore CommandOrControl + R in production.
-  // see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils
-  app.on('browser-window-created', (_, window) => {
-    optimizer.watchWindowShortcuts(window);
-  });
+  // This method will be called when Electron has finished
+  // initialization and is ready to create browser windows.
+  // Some APIs can only be used after this event occurs.
+  app.whenReady().then(() => {
+    // Set app user model id for windows
+    electronApp.setAppUserModelId('com.electron');
+
+    // Default open or close DevTools by F12 in development
+    // and ignore CommandOrControl + R in production.
+    // see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils
+    app.on('browser-window-created', (_, window) => {
+      optimizer.watchWindowShortcuts(window);
+    });
 
-  // use electron
-  useElectron();
-  // use multiple window porcess
-  useWinProcess();
+    // use electron
+    useElectron();
+    // use multiple window porcess
+    useWinProcess();
+    // 渲染进程加载好之后,会发起check-silence-authority
+    ipcMain.handleOnce('check-silence-authority', () => {
+      const argv = process.argv;
+      log.info(`check-silence-authority process.argv:${JSON.stringify(argv)}`);
+      const urlScheme = argv.find((arg) => arg.startsWith(ULR_SCHEME_PROTOCOL));
+      return urlScheme || '';
+    });
 
-  createWindow();
+    createWindow();
 
-  // protocol
-  protocol.handle('local', (request) => {
-    const filePath = request.url.slice('local://'.length);
-    return net.fetch(
-      url.pathToFileURL(resolve(__dirname, filePath)).toString()
-    );
+    // protocol
+    protocol.handle('local', (request) => {
+      const filePath = request.url.slice('local://'.length);
+      return net.fetch(
+        url.pathToFileURL(resolve(__dirname, filePath)).toString()
+      );
+    });
+
+    app.on('activate', () => {
+      // On macOS it's common to re-create a window in the app when the
+      // dock icon is clicked and there are no other windows open.
+      if (BrowserWindow.getAllWindows().length === 0) createWindow();
+    });
   });
 
-  app.on('activate', () => {
-    // On macOS it's common to re-create a window in the app when the
-    // dock icon is clicked and there are no other windows open.
-    if (BrowserWindow.getAllWindows().length === 0) createWindow();
+  // Quit when all windows are closed, except on macOS. There, it's common
+  // for applications and their menu bar to stay active until the user quits
+  // explicitly with Cmd + Q.
+  app.on('window-all-closed', () => {
+    if (process.platform !== 'darwin') {
+      app.quit();
+    }
   });
-});
 
-// Quit when all windows are closed, except on macOS. There, it's common
-// for applications and their menu bar to stay active until the user quits
-// explicitly with Cmd + Q.
-app.on('window-all-closed', () => {
-  if (process.platform !== 'darwin') {
-    app.quit();
+  // In this file you can include the rest of your app"s specific main process
+  // code. You can also put them in separate files and require them here.
+
+  // simple instance
+  app.on('second-instance', (event, argv) => {
+    silenceAuthorityHandle(argv);
+    mainWindow?.show();
+  });
+
+  // 免登录流程
+  function silenceAuthorityHandle(argv: string[]) {
+    log.info(`second-instance process.argv:${JSON.stringify(argv)}`);
+    const urlScheme = argv.find((arg) => arg.startsWith(ULR_SCHEME_PROTOCOL));
+    if (urlScheme && mainWindow) {
+      mainWindow.webContents.send('silence-authority', urlScheme);
+    }
   }
-});
+}
 
-// In this file you can include the rest of your app"s specific main process
-// code. You can also put them in separate files and require them here.
+main();

+ 6 - 0
electron/preload/apiElectron.ts

@@ -27,12 +27,16 @@ function onStopRunning(callback: () => void) {
   return ipcRenderer.on('stop-running', () => callback());
 }
 
+// silence-authority
 function onSilenceAuthority(callback: (authKey: string) => void) {
   return ipcRenderer.on(
     'silence-authority',
     (event: Electron.IpcRendererEvent, authKey: string) => callback(authKey)
   );
 }
+function checkSilenceAuthority(): Promise<string> {
+  return ipcRenderer.invoke('check-silence-authority');
+}
 
 const electronApi = {
   dialogSelectFile,
@@ -42,7 +46,9 @@ const electronApi = {
   stopWinProcess,
   closeProcessWindow,
   onStopRunning,
+  // silence-authority
   onSilenceAuthority,
+  checkSilenceAuthority,
 };
 
 export type ElectronApi = typeof electronApi;

+ 2 - 126
src/App.vue

@@ -8,132 +8,8 @@
 </template>
 
 <script lang="ts" setup>
-  import { useRoute, useRouter } from 'vue-router';
-  import { Message } from '@arco-design/web-vue';
-  import { sysMenu } from '@/api/user';
-
-  import { AESDecode } from './utils/crypto';
-  import { UserState } from './store/modules/user/types';
-  import { AppState } from './store/modules/app/types';
-  import { useUserStore, useAppStore } from './store';
-
-  const router = useRouter();
-  const route = useRoute();
-  const appStore = useAppStore();
-  const userStore = useUserStore();
+  import useSilenceAuth from './hooks/silence-auth';
 
+  const { registSilenceAuthEvent } = useSilenceAuth();
   registSilenceAuthEvent();
-
-  function registSilenceAuthEvent() {
-    window.electron.onSilenceAuthority(silenceAuthorityHandle);
-  }
-
-  function silenceAuthorityHandle(authKey: string) {
-    if (!authKey) return;
-
-    if (authKey.startsWith('trd://launcher')) {
-      silenceLauncher();
-      return;
-    }
-
-    if (authKey.startsWith('trd://download')) {
-      silenceDownload(authKey);
-      return;
-    }
-
-    window.api.logger(`error authKey data`, 'error');
-  }
-  // 打开工具登录界面
-  function silenceLauncher() {
-    if (route.name === 'Login') return;
-
-    router.push({ name: 'Login' });
-  }
-
-  interface AuthData {
-    user: Partial<UserState>;
-    apiUrl: string;
-    actionSession: AppState['actionSession'];
-    downloadSet: AppState['downloadSet'];
-  }
-
-  // 静默登录下载
-  async function silenceDownload(authKey: string) {
-    const paramData = authKey.replace('trd://download/?q=', '');
-    let authData: AuthData | null = null;
-    try {
-      authData = JSON.parse(AESDecode(paramData));
-    } catch (error) {
-      window.api.logger(`authData: 解析错误!`);
-    }
-    if (!authData) return;
-    window.api.logger(`authData: ${JSON.stringify(authData)}`);
-
-    // 防止重复执行
-    if (authData.actionSession === appStore.actionSession) {
-      window.api.logger(`[warning] 重复的actionSession!`);
-      return;
-    }
-    // 有任务在进行,不做任何处理
-    if (appStore.downloadTaskRunning) {
-      window.api.logger(`[warning] 当前有任务正在进行!`);
-      return;
-    }
-
-    // console.log(authData);
-    const user = authData.user;
-    if (!user || !user.accessToken) return;
-
-    // 登录人切换警告
-    if (userStore.id && userStore.id !== user.id) {
-      Message.warning('登录人信息改变!');
-      window.api.logger(`[warning] 登录人信息改变!`);
-    }
-
-    appStore.setInfo({
-      domain: authData.apiUrl,
-      actionSession: authData.actionSession || '',
-      downloadSet: authData.downloadSet || null,
-    });
-
-    user.privilegeId = '';
-    userStore.setInfo(user);
-
-    const menuRes = await sysMenu();
-    const validMenu = menuRes.privileges.find(
-      (item) => item.url === 'ScoreManage'
-    );
-    if (!validMenu) {
-      userStore.resetInfo();
-      appStore.setInfo({
-        actionSession: '',
-        downloadSet: null,
-      });
-      recoverSystemDomain();
-      Message.error('您没有权限!');
-      return;
-    }
-    userStore.setInfo({ privilegeId: validMenu.id });
-    await updateSystemDomain(authData.apiUrl);
-
-    if (route.name === 'TrackExport') {
-      window.api.logger(`TrackExport 页面刷新`);
-      window.location.reload();
-      return;
-    }
-
-    window.api.logger(`进入 TrackExport 页面`);
-    router.push({ name: 'TrackExport' });
-  }
-
-  async function updateSystemDomain(domain: string) {
-    await window.db.updateDict({ key: 'domain', val: domain });
-  }
-
-  async function recoverSystemDomain() {
-    const domain = await window.db.getDict('domain');
-    appStore.setInfo({
-      domain,
-    });
-  }
 </script>

+ 136 - 0
src/hooks/silence-auth.ts

@@ -0,0 +1,136 @@
+import { useRoute, useRouter } from 'vue-router';
+import { Message } from '@arco-design/web-vue';
+import { sysMenu } from '@/api/user';
+
+import { AESDecode } from '../utils/crypto';
+import { UserState } from '../store/modules/user/types';
+import { AppState } from '../store/modules/app/types';
+import { useUserStore, useAppStore } from '../store';
+
+export default function useSilenceAuth() {
+  const router = useRouter();
+  const route = useRoute();
+  const appStore = useAppStore();
+  const userStore = useUserStore();
+
+  function registSilenceAuthEvent() {
+    window.electron.onSilenceAuthority((authKey: string) => {
+      window.api.logger(`app onSilenceAuthority => authKey:${authKey}`);
+      silenceAuthorityHandle(authKey);
+    });
+  }
+
+  function silenceAuthorityHandle(authKey: string) {
+    if (!authKey) return;
+
+    if (authKey.startsWith('trd://launcher')) {
+      silenceLauncher();
+      return;
+    }
+
+    if (authKey.startsWith('trd://download')) {
+      silenceDownload(authKey);
+      return;
+    }
+
+    window.api.logger(`error authKey data`, 'error');
+  }
+  // 打开工具登录界面
+  function silenceLauncher() {
+    if (route.name === 'Login') return;
+
+    router.push({ name: 'Login' });
+  }
+
+  interface AuthData {
+    user: Partial<UserState>;
+    apiUrl: string;
+    actionSession: AppState['actionSession'];
+    downloadSet: AppState['downloadSet'];
+  }
+
+  // 静默登录下载
+  async function silenceDownload(authKey: string) {
+    const paramData = authKey.replace('trd://download/?q=', '');
+    let authData: AuthData | null = null;
+    try {
+      authData = JSON.parse(AESDecode(paramData));
+    } catch (error) {
+      window.api.logger(`authData: 解析错误!`);
+    }
+    if (!authData) return;
+    window.api.logger(`authData: ${JSON.stringify(authData)}`);
+
+    // 防止重复执行
+    if (authData.actionSession === appStore.actionSession) {
+      window.api.logger(`[warning] 重复的actionSession!`);
+      return;
+    }
+    // 有任务在进行,不做任何处理
+    if (appStore.downloadTaskRunning) {
+      window.api.logger(`[warning] 当前有任务正在进行!`);
+      return;
+    }
+
+    // console.log(authData);
+    const user = authData.user;
+    if (!user || !user.accessToken) return;
+
+    // 登录人切换警告
+    if (userStore.id && userStore.id !== user.id) {
+      Message.warning('登录人信息改变!');
+      window.api.logger(`[warning] 登录人信息改变!`);
+    }
+
+    appStore.setInfo({
+      domain: authData.apiUrl,
+      actionSession: authData.actionSession || '',
+      downloadSet: authData.downloadSet || null,
+    });
+
+    user.privilegeId = '';
+    userStore.setInfo(user);
+
+    const menuRes = await sysMenu();
+    const validMenu = menuRes.privileges.find(
+      (item) => item.url === 'ScoreManage'
+    );
+    if (!validMenu) {
+      userStore.resetInfo();
+      appStore.setInfo({
+        actionSession: '',
+        downloadSet: null,
+      });
+      recoverSystemDomain();
+      Message.error('您没有权限!');
+      return;
+    }
+    userStore.setInfo({ privilegeId: validMenu.id });
+    await updateSystemDomain(authData.apiUrl);
+
+    if (route.name === 'TrackExport') {
+      window.api.logger(`TrackExport 页面刷新`);
+      window.location.reload();
+      return;
+    }
+
+    window.api.logger(`进入 TrackExport 页面`);
+    router.push({ name: 'TrackExport' });
+  }
+
+  async function updateSystemDomain(domain: string) {
+    await window.db.updateDict({ key: 'domain', val: domain });
+  }
+
+  async function recoverSystemDomain() {
+    const domain = await window.db.getDict('domain');
+    appStore.setInfo({
+      domain,
+    });
+  }
+
+  return {
+    registSilenceAuthEvent,
+    silenceAuthorityHandle,
+  };
+}

+ 1 - 1
src/router/index.ts

@@ -13,7 +13,7 @@ const router = createRouter({
   routes: [
     {
       path: '/',
-      redirect: { name: 'Login' },
+      redirect: { name: 'Wait' },
     },
     ...appRoutes,
     ...appExternalRoutes,

+ 19 - 8
src/router/routes/externalModules/system.ts

@@ -1,14 +1,25 @@
 import { DEFAULT_ROUTE_NAME } from '../../constants';
 import { AppRouteRecordRaw } from '../types';
 
-const SYSTEM: AppRouteRecordRaw = {
-  path: '/workplace',
-  name: DEFAULT_ROUTE_NAME,
-  component: () => import('@/views/system/workplace/index.vue'),
-  meta: {
-    requiresAuth: true,
-    title: '主系统',
+const SYSTEM: AppRouteRecordRaw[] = [
+  {
+    path: '/workplace',
+    name: DEFAULT_ROUTE_NAME,
+    component: () => import('@/views/system/workplace/index.vue'),
+    meta: {
+      requiresAuth: true,
+      title: '主系统',
+    },
   },
-};
+  {
+    path: '/wait',
+    name: 'Wait',
+    component: () => import('@/views/system/wait/index.vue'),
+    meta: {
+      requiresAuth: false,
+      title: '等待',
+    },
+  },
+];
 
 export default SYSTEM;

+ 24 - 0
src/views/system/wait/index.vue

@@ -0,0 +1,24 @@
+<template>
+  <div></div>
+</template>
+
+<script lang="ts" setup>
+  import { useRouter } from 'vue-router';
+  import useSilenceAuth from '@/hooks/silence-auth';
+
+  const router = useRouter();
+
+  const { silenceAuthorityHandle } = useSilenceAuth();
+
+  async function checkSilenceAuth() {
+    const url = await window.electron.checkSilenceAuthority();
+    window.api.logger(`wait checkSilenceAuth: ${url}`);
+    if (!url) {
+      router.push({ name: 'Login' });
+      return;
+    }
+
+    silenceAuthorityHandle(url);
+  }
+  checkSilenceAuth();
+</script>