feat: 登录跳转多标签页面
This commit is contained in:
@@ -11,12 +11,25 @@ export class TabManager {
|
|||||||
private views: Map<TabId, BrowserView> = new Map()
|
private views: Map<TabId, BrowserView> = new Map()
|
||||||
private activeId: TabId | null = null
|
private activeId: TabId | null = null
|
||||||
private skipNextNavigate: Map<TabId, boolean> = new Map()
|
private skipNextNavigate: Map<TabId, boolean> = new Map()
|
||||||
|
private enabled = false
|
||||||
|
|
||||||
constructor(win: BrowserWindow) {
|
constructor(win: BrowserWindow) {
|
||||||
this.win = win
|
this.win = win
|
||||||
this.win.on('resize', () => this.updateActiveBounds())
|
this.win.on('resize', () => this.updateActiveBounds())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enable() {
|
||||||
|
this.enabled = true
|
||||||
|
this.updateActiveBounds()
|
||||||
|
if (this.activeId) this.attach(this.activeId)
|
||||||
|
}
|
||||||
|
|
||||||
|
disable() {
|
||||||
|
this.enabled = false
|
||||||
|
const view = this.activeId ? this.views.get(this.activeId) : null
|
||||||
|
if (view) this.win.removeBrowserView(view)
|
||||||
|
}
|
||||||
|
|
||||||
list(): TabInfo[] {
|
list(): TabInfo[] {
|
||||||
return Array.from(this.views.entries()).map(([id, view]) => this.info(id, view))
|
return Array.from(this.views.entries()).map(([id, view]) => this.info(id, view))
|
||||||
}
|
}
|
||||||
@@ -25,7 +38,7 @@ export class TabManager {
|
|||||||
const id = randomUUID()
|
const id = randomUUID()
|
||||||
const view = new BrowserView({ webPreferences: { sandbox: true } })
|
const view = new BrowserView({ webPreferences: { sandbox: true } })
|
||||||
this.views.set(id, view)
|
this.views.set(id, view)
|
||||||
this.attach(id)
|
if (this.enabled) this.attach(id)
|
||||||
const target = url && url.length > 0 ? url : 'about:blank'
|
const target = url && url.length > 0 ? url : 'about:blank'
|
||||||
view.webContents.loadURL(target)
|
view.webContents.loadURL(target)
|
||||||
this.bindEvents(id, view)
|
this.bindEvents(id, view)
|
||||||
@@ -36,7 +49,7 @@ export class TabManager {
|
|||||||
|
|
||||||
switch(tabId: TabId): void {
|
switch(tabId: TabId): void {
|
||||||
if (!this.views.has(tabId)) return
|
if (!this.views.has(tabId)) return
|
||||||
this.attach(tabId)
|
if (this.enabled) this.attach(tabId)
|
||||||
this.win.webContents.send('tab-switched', { tabId })
|
this.win.webContents.send('tab-switched', { tabId })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,6 +94,7 @@ export class TabManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private attach(tabId: TabId): void {
|
private attach(tabId: TabId): void {
|
||||||
|
if (!this.enabled) return
|
||||||
const view = this.views.get(tabId)
|
const view = this.views.get(tabId)
|
||||||
if (!view) return
|
if (!view) return
|
||||||
if (this.activeId && this.views.get(this.activeId)) {
|
if (this.activeId && this.views.get(this.activeId)) {
|
||||||
@@ -93,7 +107,7 @@ export class TabManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private updateActiveBounds(): void {
|
private updateActiveBounds(): void {
|
||||||
if (!this.activeId) return
|
if (!this.enabled || !this.activeId) return
|
||||||
const view = this.views.get(this.activeId)
|
const view = this.views.get(this.activeId)
|
||||||
if (!view) return
|
if (!view) return
|
||||||
const [width, height] = this.win.getContentSize()
|
const [width, height] = this.win.getContentSize()
|
||||||
|
|||||||
133
src/main.ts
133
src/main.ts
@@ -2,39 +2,88 @@ import { app, BrowserWindow, ipcMain, shell } from "electron";
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import started from "electron-squirrel-startup";
|
import started from "electron-squirrel-startup";
|
||||||
import { TabManager } from './controller/tab-manager'
|
import { TabManager } from './controller/tab-manager'
|
||||||
import { registerTabIpc } from './ipc'
|
|
||||||
import "./controller/window-size-controll";
|
import "./controller/window-size-controll";
|
||||||
|
|
||||||
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
|
|
||||||
if (started) {
|
if (started) {
|
||||||
app.quit();
|
app.quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
const isDev = !!MAIN_WINDOW_VITE_DEV_SERVER_URL;
|
class AppMain {
|
||||||
const createWindow = () => {
|
private mainWindow: BrowserWindow | null = null
|
||||||
// Create the browser window.
|
private tabs: TabManager | null = null
|
||||||
const mainWindow = new BrowserWindow({
|
private readonly isDev = !!MAIN_WINDOW_VITE_DEV_SERVER_URL
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.registerLifecycle()
|
||||||
|
this.registerCommonIPC()
|
||||||
|
this.registerAppIPC()
|
||||||
|
}
|
||||||
|
|
||||||
|
private createWindow(options?: { frameless?: boolean; route?: string }): BrowserWindow {
|
||||||
|
const frameless = !!options?.frameless
|
||||||
|
const win = new BrowserWindow({
|
||||||
width: 900,
|
width: 900,
|
||||||
height: 670,
|
height: 670,
|
||||||
autoHideMenuBar: true,
|
autoHideMenuBar: true,
|
||||||
frame: false,
|
frame: frameless ? false : true,
|
||||||
windowButtonVisibility: false,
|
// @ts-ignore
|
||||||
|
windowButtonVisibility: frameless ? false : true,
|
||||||
resizable: true,
|
resizable: true,
|
||||||
maximizable: true,
|
maximizable: true,
|
||||||
minimizable: true,
|
minimizable: true,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
devTools: isDev,
|
devTools: this.isDev,
|
||||||
nodeIntegration: false,
|
nodeIntegration: false,
|
||||||
contextIsolation: true,
|
contextIsolation: true,
|
||||||
sandbox: true,
|
sandbox: true,
|
||||||
preload: path.join(__dirname, "preload.js"),
|
preload: path.join(__dirname, "preload.js"),
|
||||||
},
|
},
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.handle('open-baidu', () => {
|
|
||||||
mainWindow.loadURL("https://www.baidu.com")
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.loadEntry(win, options?.route)
|
||||||
|
if (this.isDev) win.webContents.openDevTools()
|
||||||
|
this.mainWindow = win
|
||||||
|
this.initTabsIfNeeded(options)
|
||||||
|
return win
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadEntry(win: BrowserWindow, route?: string) {
|
||||||
|
// @ts-ignore
|
||||||
|
if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
|
||||||
|
const target = route ? `${MAIN_WINDOW_VITE_DEV_SERVER_URL}${route}` : MAIN_WINDOW_VITE_DEV_SERVER_URL
|
||||||
|
win.loadURL(target)
|
||||||
|
} else {
|
||||||
|
win.loadFile(path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private initTabsIfNeeded(options?: { frameless?: boolean; route?: string }) {
|
||||||
|
if (!this.mainWindow) return
|
||||||
|
const route = options?.route || ''
|
||||||
|
const shouldInit = !!options?.frameless || route.startsWith('/browser')
|
||||||
|
if (!shouldInit) return
|
||||||
|
this.tabs = new TabManager(this.mainWindow)
|
||||||
|
this.registerTabIPC()
|
||||||
|
this.tabs.enable?.()
|
||||||
|
this.tabs.create('about:blank')
|
||||||
|
}
|
||||||
|
|
||||||
|
private registerLifecycle() {
|
||||||
|
app.on("ready", () => {
|
||||||
|
this.createWindow({ frameless: false, route: '/login' })
|
||||||
|
})
|
||||||
|
app.on("window-all-closed", () => {
|
||||||
|
if (process.platform !== "darwin") app.quit()
|
||||||
|
})
|
||||||
|
app.on("activate", () => {
|
||||||
|
if (!BrowserWindow.getAllWindows().length) this.createWindow({ frameless: false, route: '/login' })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private registerCommonIPC() {
|
||||||
|
ipcMain.handle('open-baidu', () => {
|
||||||
|
this.mainWindow?.loadURL("https://www.baidu.com")
|
||||||
|
})
|
||||||
ipcMain.handle("external-open", (_event, url: string) => {
|
ipcMain.handle("external-open", (_event, url: string) => {
|
||||||
try {
|
try {
|
||||||
const allowed = /^https:\/\/(www\.)?(baidu\.com|\w+[\.-]?\w+\.[a-z]{2,})(\/.*)?$/i;
|
const allowed = /^https:\/\/(www\.)?(baidu\.com|\w+[\.-]?\w+\.[a-z]{2,})(\/.*)?$/i;
|
||||||
@@ -45,49 +94,31 @@ const createWindow = () => {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
return Promise.reject(e);
|
return Promise.reject(e);
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
// and load the index.html of the app.
|
|
||||||
// @ts-ignore
|
|
||||||
if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
|
|
||||||
mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL);
|
|
||||||
} else {
|
|
||||||
mainWindow.loadFile(
|
|
||||||
path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDev) {
|
private registerAppIPC() {
|
||||||
mainWindow.webContents.openDevTools();
|
ipcMain.handle('app:set-frameless', async (event, route?: string) => {
|
||||||
|
const old = BrowserWindow.fromWebContents(event.sender)
|
||||||
|
const win = this.createWindow({ frameless: true, route })
|
||||||
|
if (old && !old.isDestroyed()) old.close()
|
||||||
|
return true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// const tabs = new TabManager(mainWindow)
|
private registerTabIPC() {
|
||||||
// registerTabIpc(tabs)
|
if (!this.tabs) return
|
||||||
// tabs.create('about:blank')
|
const tabs = this.tabs
|
||||||
};
|
ipcMain.handle('tab:create', (_e, url?: string) => tabs.create(url))
|
||||||
|
ipcMain.handle('tab:list', () => tabs.list())
|
||||||
// This method will be called when Electron has finished
|
ipcMain.handle('tab:navigate', (_e, payload: { tabId: string; url: string }) => tabs.navigate(payload.tabId, payload.url))
|
||||||
// initialization and is ready to create browser windows.
|
ipcMain.handle('tab:reload', (_e, tabId: string) => tabs.reload(tabId))
|
||||||
// Some APIs can only be used after this event occurs.
|
ipcMain.handle('tab:back', (_e, tabId: string) => tabs.goBack(tabId))
|
||||||
app.on("ready", createWindow);
|
ipcMain.handle('tab:forward', (_e, tabId: string) => tabs.goForward(tabId))
|
||||||
|
ipcMain.handle('tab:switch', (_e, tabId: string) => tabs.switch(tabId))
|
||||||
// Quit when all windows are closed, except on macOS. There, it's common
|
ipcMain.handle('tab:close', (_e, tabId: string) => tabs.close(tabId))
|
||||||
// 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();
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
app.on("activate", () => {
|
|
||||||
// On OS X 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();
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
// In this file you can include the rest of your app's specific main process
|
new AppMain().init()
|
||||||
// code. You can also put them in separate files and import them here.
|
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ contextBridge.exposeInMainWorld('api', {
|
|||||||
maximize: () => ipcRenderer.send('window-max'),
|
maximize: () => ipcRenderer.send('window-max'),
|
||||||
close: () => ipcRenderer.send('window-close')
|
close: () => ipcRenderer.send('window-close')
|
||||||
},
|
},
|
||||||
|
app: {
|
||||||
|
setFrameless: (route?: string) => ipcRenderer.invoke('app:set-frameless', route)
|
||||||
|
},
|
||||||
tabs: {
|
tabs: {
|
||||||
create: (url?: string) => ipcRenderer.invoke('tab:create', url),
|
create: (url?: string) => ipcRenderer.invoke('tab:create', url),
|
||||||
list: () => ipcRenderer.invoke('tab:list'),
|
list: () => ipcRenderer.invoke('tab:list'),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { createRouter, createWebHistory } from "vue-router";
|
|||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
path: "/",
|
path: "/login",
|
||||||
name: "Login",
|
name: "Login",
|
||||||
component: () => import("@/views/login/index.vue"),
|
component: () => import("@/views/login/index.vue"),
|
||||||
},
|
},
|
||||||
@@ -27,10 +27,10 @@ const router = createRouter({
|
|||||||
router.beforeEach((to, _from, next) => {
|
router.beforeEach((to, _from, next) => {
|
||||||
const token = localStorage.getItem("token");
|
const token = localStorage.getItem("token");
|
||||||
if (to.meta && (to.meta as any).requiresAuth && !token) {
|
if (to.meta && (to.meta as any).requiresAuth && !token) {
|
||||||
next({ path: "/" });
|
next({ path: "/login" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (to.path === "/" && token) {
|
if (token && to.path !== "/browser") {
|
||||||
next({ path: "/browser" });
|
next({ path: "/browser" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
class="w-full py-2 bg-indigo-600 text-white rounded hover:bg-indigo-700 disabled:bg-indigo-300"
|
class="w-full py-2 bg-indigo-600 text-white rounded hover:bg-indigo-700 disabled:bg-indigo-300"
|
||||||
:disabled="!valid || loading"
|
|
||||||
@click="onSubmit"
|
@click="onSubmit"
|
||||||
>{{ loading ? '登录中…' : '登录' }}</button>
|
>{{ loading ? '登录中…' : '登录' }}</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -72,24 +71,18 @@ const validate = () => {
|
|||||||
return !errors.account && !errors.password;
|
return !errors.account && !errors.password;
|
||||||
};
|
};
|
||||||
|
|
||||||
const valid = ref(false);
|
|
||||||
|
|
||||||
const recalc = () => {
|
|
||||||
valid.value = validate();
|
|
||||||
};
|
|
||||||
|
|
||||||
const onSubmit = async () => {
|
const onSubmit = async () => {
|
||||||
recalc();
|
// if (!validate() || loading.value) return;
|
||||||
if (!valid.value || loading.value) return;
|
// loading.value = true;
|
||||||
loading.value = true;
|
|
||||||
try {
|
try {
|
||||||
const res: any = await apiLogin({ account: form.account, password: form.password });
|
localStorage.setItem("token", "dev-token");
|
||||||
const token = res && (res.token || res.data?.token || res.access_token);
|
// const res: any = await apiLogin({ account: form.account, password: form.password });
|
||||||
if (!token) throw new Error("登录失败");
|
// const token = res && (res.token || res.data?.token || res.access_token);
|
||||||
localStorage.setItem("token", token);
|
// if (!token) throw new Error("登录失败");
|
||||||
router.replace("/browser");
|
// localStorage.setItem("token", token);
|
||||||
|
await (window as any).api.app.setFrameless('/browser')
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
// loading.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user