7 Commits

Author SHA1 Message Date
duanshuwen
768c0285ee feat: 代码优化 2025-12-07 21:12:34 +08:00
duanshuwen
9379a5d6bc feat: 新增系统设置页面 2025-12-07 20:56:20 +08:00
duanshuwen
1d8ee0bf64 feat: 新增账号设置组件 2025-12-07 19:58:03 +08:00
duanshuwen
b643972d21 feat: 调整布局 2025-12-07 16:37:24 +08:00
duanshuwen
84300791a0 feat: 调整布局 2025-12-07 16:29:57 +08:00
duanshuwen
14d916187c feat: 新增页面 2025-12-07 14:54:52 +08:00
duanshuwen
da7024747a feat: 调整布局 2025-12-07 09:34:11 +08:00
37 changed files with 950 additions and 207 deletions

405
package-lock.json generated
View File

@@ -44,6 +44,7 @@
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-vue": "^10.6.0",
"postcss": "^8.5.6",
"sass-embedded": "^1.89.1",
"tailwindcss": "^3.4.18",
"typescript": "~4.5.4",
"vite": "^7.1.9",
@@ -109,6 +110,13 @@
"node": ">=6.9.0"
}
},
"node_modules/@bufbuild/protobuf": {
"version": "2.10.1",
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.10.1.tgz",
"integrity": "sha512-ckS3+vyJb5qGpEYv/s1OebUHDi/xSNtfgw1wqKZo7MR9F2z+qXr0q5XagafAG/9O0QPVIUfST0smluYSTpYFkg==",
"dev": true,
"license": "(Apache-2.0 AND BSD-3-Clause)"
},
"node_modules/@ctrl/tinycolor": {
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
@@ -3923,6 +3931,13 @@
"ieee754": "^1.1.13"
}
},
"node_modules/buffer-builder": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/buffer-builder/-/buffer-builder-0.2.0.tgz",
"integrity": "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==",
"dev": true,
"license": "MIT/X11"
},
"node_modules/buffer-crc32": {
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
@@ -4405,6 +4420,13 @@
"dev": true,
"license": "MIT"
},
"node_modules/colorjs.io": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz",
"integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==",
"dev": true,
"license": "MIT"
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -7212,6 +7234,13 @@
"node": ">= 4"
}
},
"node_modules/immutable": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz",
"integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==",
"dev": true,
"license": "MIT"
},
"node_modules/import-fresh": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
@@ -10459,6 +10488,23 @@
"queue-microtask": "^1.2.2"
}
},
"node_modules/rxjs": {
"version": "7.8.2",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.1.0"
}
},
"node_modules/rxjs/node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"dev": true,
"license": "0BSD"
},
"node_modules/safe-array-concat": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz",
@@ -10542,6 +10588,335 @@
"dev": true,
"license": "MIT"
},
"node_modules/sass-embedded": {
"version": "1.89.1",
"resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.89.1.tgz",
"integrity": "sha512-alvGGlyYdkSXYKOfS/TTxUD0993EYOe3adIPtwCWEg037qe183p2dkYnbaRsCLJFKt+QoyRzhsrbCsK7sbR6MA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@bufbuild/protobuf": "^2.0.0",
"buffer-builder": "^0.2.0",
"colorjs.io": "^0.5.0",
"immutable": "^5.0.2",
"rxjs": "^7.4.0",
"supports-color": "^8.1.1",
"sync-child-process": "^1.0.2",
"varint": "^6.0.0"
},
"bin": {
"sass": "dist/bin/sass.js"
},
"engines": {
"node": ">=16.0.0"
},
"optionalDependencies": {
"sass-embedded-android-arm": "1.89.1",
"sass-embedded-android-arm64": "1.89.1",
"sass-embedded-android-riscv64": "1.89.1",
"sass-embedded-android-x64": "1.89.1",
"sass-embedded-darwin-arm64": "1.89.1",
"sass-embedded-darwin-x64": "1.89.1",
"sass-embedded-linux-arm": "1.89.1",
"sass-embedded-linux-arm64": "1.89.1",
"sass-embedded-linux-musl-arm": "1.89.1",
"sass-embedded-linux-musl-arm64": "1.89.1",
"sass-embedded-linux-musl-riscv64": "1.89.1",
"sass-embedded-linux-musl-x64": "1.89.1",
"sass-embedded-linux-riscv64": "1.89.1",
"sass-embedded-linux-x64": "1.89.1",
"sass-embedded-win32-arm64": "1.89.1",
"sass-embedded-win32-x64": "1.89.1"
}
},
"node_modules/sass-embedded-android-arm": {
"version": "1.89.1",
"resolved": "https://registry.npmjs.org/sass-embedded-android-arm/-/sass-embedded-android-arm-1.89.1.tgz",
"integrity": "sha512-wVchZSz8zbJBwwOs9/iwco/M5G3L5BaeqwUF1EC3Gtzn1BsXYUEkJfftW2HxGl4hQz2YlpR7BY1GRN817uxADA==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/sass-embedded-android-arm64": {
"version": "1.89.1",
"resolved": "https://registry.npmjs.org/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.89.1.tgz",
"integrity": "sha512-Je6x7uuJRGQdr5ziSJdaPA4NhBSO26BU/E55qiuMUZpjq2EWBEJPbNeugu/cWlCEmfqoVuxj37r8aEU+KG0H1g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/sass-embedded-android-riscv64": {
"version": "1.89.1",
"resolved": "https://registry.npmjs.org/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.89.1.tgz",
"integrity": "sha512-DhWe+A4RVtpHMVaQgdzRpiczAXKPl7XhyY9USkY9Xkhv94+csTfjyuFmsUuCpKSiQDQkD+rGByfg+9yQIk/RgQ==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/sass-embedded-android-x64": {
"version": "1.89.1",
"resolved": "https://registry.npmjs.org/sass-embedded-android-x64/-/sass-embedded-android-x64-1.89.1.tgz",
"integrity": "sha512-LTEzxTXrv3evPiHBmDMtJtO5tEprg7bvNOwYTjDEhE9ZCYdb70l+haIY0dVyhGxyeaBJlyvatjWOKEduPP3Lyw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/sass-embedded-darwin-arm64": {
"version": "1.89.1",
"resolved": "https://registry.npmjs.org/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.89.1.tgz",
"integrity": "sha512-7qMO4BLdIOFMMc1M+hg5iWEjPxbPlH1XTPUCwyuXYqubz6kXkdrrtJXolNAAey/0ZOE6uXk0APugm93a/veQdQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/sass-embedded-darwin-x64": {
"version": "1.89.1",
"resolved": "https://registry.npmjs.org/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.89.1.tgz",
"integrity": "sha512-Jzuws3NNx4YtDdL2/skP8BvGqMBKn26XINehwLnD2kgbh0+k+vKNWt5JDomvIuZVLsK8zWrMoRkXpk4wuHdqrw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/sass-embedded-linux-arm": {
"version": "1.89.1",
"resolved": "https://registry.npmjs.org/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.89.1.tgz",
"integrity": "sha512-8TvFr/lh7FARtNr9mM57m7NNvtSZwnlkXtfY1D48B81Ve6GgtLqQhELNzvTcfQ0WZa0aNnVjq9XUuWLlrMDaZQ==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/sass-embedded-linux-arm64": {
"version": "1.89.1",
"resolved": "https://registry.npmjs.org/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.89.1.tgz",
"integrity": "sha512-h967EV2armjV+Re+hHv7LaIzCOvV6DoFod9GJhXTdnPvilqs7DAPTUfN07wOqbzjlaGEnITZXzLsWAoZ1Z7tWQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/sass-embedded-linux-musl-arm": {
"version": "1.89.1",
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.89.1.tgz",
"integrity": "sha512-Tl8wDL+3qFa/AhvZZBb1OvhN1SvIsRSLaPdGP8cv3VmKKVBdlLp2zedPTlcLJpR9dG/bjtGJYGX15kWHAvZ6mQ==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/sass-embedded-linux-musl-arm64": {
"version": "1.89.1",
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.89.1.tgz",
"integrity": "sha512-l4TrsUmE3AEPy2gDThb+OQV5xSyrb807DJbkQiFtTwvtOZAAkoVl1v2QeocW0npgKjc/W7nHMiSempJe0UcV7w==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/sass-embedded-linux-musl-riscv64": {
"version": "1.89.1",
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.89.1.tgz",
"integrity": "sha512-YJVZmz032U7dv4RW3u+SJGp+DQWmYWc5fX/aXzLuoL6PPUPon1/Sseaf/5YGtcuQf8RnxZBbM2nFHFVHDJfsQw==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/sass-embedded-linux-musl-x64": {
"version": "1.89.1",
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.89.1.tgz",
"integrity": "sha512-67ijpk87V0VlpdVTtgnfIzRkVUMtEH79nvGctvNpk0XT6v+oxoFRljFRiYItZOxb5gRZMnvtkgaz1VHVcMrhtg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/sass-embedded-linux-riscv64": {
"version": "1.89.1",
"resolved": "https://registry.npmjs.org/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.89.1.tgz",
"integrity": "sha512-SQNWy5kUvlQJUKRXFy8jS05DBik+2ERIWDxOBk+QuJYEIktlA9fKKBU8c7RkgpZFNXSXZa0W1Gy27oOFCzhhuA==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/sass-embedded-linux-x64": {
"version": "1.89.1",
"resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.89.1.tgz",
"integrity": "sha512-KUqGzBvTDZG6D3Pq41sCzqO1wkxM0WmxxlI7PTuVkvgciTywHf8F7mkg2alMLVZQ6APJEYtlnCGQgn4cCgYsqw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/sass-embedded-win32-arm64": {
"version": "1.89.1",
"resolved": "https://registry.npmjs.org/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.89.1.tgz",
"integrity": "sha512-Lk6dYA18RasZxQhShT91G7Z2o7+F9necTNJ951a5AICsSJpTbg3tTnAGB7Rvd6xB5reQSZoXfB/zXKEKwtzaow==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/sass-embedded-win32-x64": {
"version": "1.89.1",
"resolved": "https://registry.npmjs.org/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.89.1.tgz",
"integrity": "sha512-YlvzrzFPHd4GKa04jMfP0t2DGJHPTm7zN4GEYtaOFqeS6BoEAUY5kBNYFy7zhwKesN3kGyU/D9rz1MfLRgGv0g==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/sass-embedded/node_modules/supports-color": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
"node_modules/schema-utils": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz",
@@ -11294,6 +11669,29 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/sync-child-process": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/sync-child-process/-/sync-child-process-1.0.2.tgz",
"integrity": "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==",
"dev": true,
"license": "MIT",
"dependencies": {
"sync-message-port": "^1.0.0"
},
"engines": {
"node": ">=16.0.0"
}
},
"node_modules/sync-message-port": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/sync-message-port/-/sync-message-port-1.1.3.tgz",
"integrity": "sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=16.0.0"
}
},
"node_modules/tailwindcss": {
"version": "3.4.18",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.18.tgz",
@@ -11945,6 +12343,13 @@
"spdx-expression-parse": "^3.0.0"
}
},
"node_modules/varint": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz",
"integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==",
"dev": true,
"license": "MIT"
},
"node_modules/vite": {
"version": "7.2.4",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.2.4.tgz",

View File

@@ -43,6 +43,7 @@
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-vue": "^10.6.0",
"postcss": "^8.5.6",
"sass-embedded": "^1.89.1",
"tailwindcss": "^3.4.18",
"typescript": "~4.5.4",
"vite": "^7.1.9",

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@@ -1,7 +1,6 @@
import { app, BrowserWindow, ipcMain, shell } from "electron";
import { app, BrowserWindow, ipcMain } from "electron";
import path from "node:path";
import started from "electron-squirrel-startup";
import { TabManager } from '@modules/tab-manager'
import { logger } from '@modules/logger'
import "@modules/window-size";
@@ -11,14 +10,12 @@ if (started) {
class AppMain {
private mainWindow: BrowserWindow | null = null
private tabs: TabManager | null = null
private readonly isDev = !!MAIN_WINDOW_VITE_DEV_SERVER_URL
init() {
this.registerLifecycle()
this.registerCommonIPC()
this.registerAppIPC()
this.registerLogIPC()
// this.registerLogIPC()
}
private createWindow(options?: { frameless?: boolean; route?: string }): BrowserWindow {
@@ -45,7 +42,6 @@ class AppMain {
this.loadEntry(win, options?.route)
if (this.isDev) win.webContents.openDevTools()
this.mainWindow = win
this.initTabsIfNeeded(options)
return win
}
@@ -65,17 +61,6 @@ class AppMain {
});
}
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' })
@@ -88,23 +73,6 @@ class AppMain {
})
}
private registerCommonIPC() {
ipcMain.handle('open-baidu', () => {
this.mainWindow?.loadURL("https://www.baidu.com")
})
ipcMain.handle("external-open", (_event, url: string) => {
try {
const allowed = /^https:\/\/(www\.)?(baidu\.com|\w+[\.-]?\w+\.[a-z]{2,})(\/.*)?$/i;
if (typeof url === "string" && allowed.test(url)) {
return shell.openExternal(url);
}
throw new Error("URL not allowed");
} catch (e) {
return Promise.reject(e);
}
})
}
private registerAppIPC() {
ipcMain.handle('app:set-frameless', async (event, route?: string) => {
const old = BrowserWindow.fromWebContents(event.sender)
@@ -114,19 +82,6 @@ class AppMain {
})
}
private registerTabIPC() {
if (!this.tabs) return
const tabs = this.tabs
ipcMain.handle('tab:create', (_e, url?: string) => tabs.create(url))
ipcMain.handle('tab:list', () => tabs.list())
ipcMain.handle('tab:navigate', (_e, payload: { tabId: string; url: string }) => tabs.navigate(payload.tabId, payload.url))
ipcMain.handle('tab:reload', (_e, tabId: string) => tabs.reload(tabId))
ipcMain.handle('tab:back', (_e, tabId: string) => tabs.goBack(tabId))
ipcMain.handle('tab:forward', (_e, tabId: string) => tabs.goForward(tabId))
ipcMain.handle('tab:switch', (_e, tabId: string) => tabs.switch(tabId))
ipcMain.handle('tab:close', (_e, tabId: string) => tabs.close(tabId))
}
private registerLogIPC() {
ipcMain.handle('log-to-main', (_e, logLevel: string, message: string) => {
switch(logLevel) {

View File

@@ -2,8 +2,6 @@ import { contextBridge, ipcRenderer } from 'electron'
import { IPCChannel, IPCAPI } from '@/shared/types/ipc.types'
const api: IPCAPI = {
openBaidu: () => ipcRenderer.invoke('open-baidu'),
versions: process.versions,
external: {
@@ -66,6 +64,9 @@ const api: IPCAPI = {
// 发送日志
logToMain: (logLevel: string, message: string) => ipcRenderer.send('log-to-main', logLevel, message),
// 打开新窗口
openNewTab: (url: string) => ipcRenderer.invoke('open-new-tab', url)
}
contextBridge.exposeInMainWorld('ipcAPI', api)

View File

@@ -1,18 +1,5 @@
<template>
<div id="app">
<router-view />
</div>
</template>
<script setup lang="ts">
import { useCounterStore } from "@store/counter";
// 使用 Pinia store
const counterStore = useCounterStore();
</script>
<style scoped>
#app {
@apply min-h-screen bg-gray-100;
}
</style>
<script setup lang="ts"></script>

View File

@@ -2,21 +2,39 @@
<div class="h-screen w-screen bg-gray-100">
<div class="flex items-center gap-1 px-3 h-10 border-b bg-white" style="-webkit-app-region: drag">
<div class="flex items-center gap-2 mr-2">
<button class="w-3 h-3 rounded-full bg-red-500" style="-webkit-app-region: no-drag" @click="onCloseWindow"></button>
<button class="w-3 h-3 rounded-full bg-yellow-400" style="-webkit-app-region: no-drag" @click="onMinimizeWindow"></button>
<button class="w-3 h-3 rounded-full bg-green-500" style="-webkit-app-region: no-drag" @click="onMaximizeWindow"></button>
<button class="w-3 h-3 rounded-full bg-red-500" style="-webkit-app-region: no-drag" @click="onCloseWindow">
<RiCloseLine />
</button>
<button class="w-3 h-3 rounded-full bg-yellow-400" style="-webkit-app-region: no-drag"
@click="onMinimizeWindow">
<RiSubtractLine />
</button>
<button class="w-3 h-3 rounded-full bg-green-500" style="-webkit-app-region: no-drag" @click="onMaximizeWindow">
<RiSquareLine />
</button>
</div>
<div v-for="t in tabs" :key="t.id" class="flex items-center px-2 py-1 rounded cursor-pointer" :class="t.id===activeId?'bg-blue-100':'hover:bg-gray-200'" @click="onSwitch(t.id)" style="-webkit-app-region: no-drag">
<div v-for="t in tabs" :key="t.id" class="flex items-center px-2 py-1 rounded cursor-pointer"
:class="t.id === activeId ? 'bg-blue-100' : 'hover:bg-gray-200'" @click="onSwitch(t.id)"
style="-webkit-app-region: no-drag">
<span class="text-sm mr-2 truncate max-w-[160px]">{{ t.title || t.url || '新标签页' }}</span>
<button class="text-gray-600 hover:text-black" style="-webkit-app-region: no-drag" @click.stop="onCloseTabId(t.id)"></button>
<button class="text-gray-600 hover:text-black" style="-webkit-app-region: no-drag"
@click.stop="onCloseTabId(t.id)"></button>
</div>
<button class="ml-2 px-2 py-1 rounded bg-gray-200 hover:bg-gray-300" style="-webkit-app-region: no-drag" @click="onNewTab"></button>
<button class="ml-2 px-2 py-1 rounded bg-gray-200 hover:bg-gray-300" style="-webkit-app-region: no-drag"
@click="onNewTab"></button>
</div>
<div class="flex items-center gap-2 px-3 h-12 border-b bg-gray-50">
<button class="px-2 py-1 bg-gray-200 rounded" @click="onBack" :disabled="!active?.canGoBack"></button>
<button class="px-2 py-1 bg-gray-200 rounded" @click="onForward" :disabled="!active?.canGoForward"></button>
<button class="px-2 py-1 bg-gray-200 rounded" @click="onReload"></button>
<input class="flex-1 px-3 py-1 border rounded-full" v-model="address" @keyup.enter="onNavigate" placeholder="输入地址后回车" />
<button class="px-2 py-1 bg-gray-200 rounded" @click="onBack" :disabled="!active?.canGoBack">
<RiArrowLeftSLine />
</button>
<button class="px-2 py-1 bg-gray-200 rounded" @click="onForward" :disabled="!active?.canGoForward">
<RiArrowRightSLine />
</button>
<button class="px-2 py-1 bg-gray-200 rounded" @click="onReload">
<RiResetLeftLine />
</button>
<input class="flex-1 px-3 py-1 border rounded-full" v-model="address" @keyup.enter="onNavigate"
placeholder="输入地址后回车" />
</div>
<div class="h-[calc(100vh-5.5rem)]"></div>
</div>
@@ -24,6 +42,7 @@
<script setup lang="ts">
import { reactive, ref, onMounted, computed } from 'vue'
import { RiArrowRightSLine, RiArrowLeftSLine, RiResetLeftLine, RiCloseLine, RiSubtractLine, RiSquareLine } from '@remixicon/vue'
type TabInfo = { id: string; url: string; title: string; isLoading: boolean; canGoBack: boolean; canGoForward: boolean }
@@ -99,18 +118,18 @@ const onMaximizeWindow = () => (window as any).ipcAPI.window.maximize()
onMounted(async () => {
await syncList()
;(window as any).ipcAPI.tabs.on('tab-created', (info: TabInfo) => {
; (window as any).ipcAPI.tabs.on('tab-created', (info: TabInfo) => {
const i = tabs.findIndex(t => t.id === info.id)
if (i === -1) tabs.push(info)
activeId.value = info.id
refreshActiveAddress()
})
;(window as any).ipcAPI.tabs.on('tab-updated', (info: TabInfo) => {
; (window as any).ipcAPI.tabs.on('tab-updated', (info: TabInfo) => {
const i = tabs.findIndex(t => t.id === info.id)
if (i >= 0) tabs[i] = info
if (activeId.value === info.id) refreshActiveAddress()
})
;(window as any).ipcAPI.tabs.on('tab-closed', ({ tabId }: { tabId: string }) => {
; (window as any).ipcAPI.tabs.on('tab-closed', ({ tabId }: { tabId: string }) => {
const i = tabs.findIndex(t => t.id === tabId)
if (i >= 0) tabs.splice(i, 1)
if (activeId.value === tabId) {
@@ -119,12 +138,11 @@ onMounted(async () => {
refreshActiveAddress()
}
})
;(window as any).ipcAPI.tabs.on('tab-switched', ({ tabId }: { tabId: string }) => {
; (window as any).ipcAPI.tabs.on('tab-switched', ({ tabId }: { tabId: string }) => {
activeId.value = tabId
refreshActiveAddress()
})
})
</script>
<style scoped>
</style>
<style scoped></style>

View File

@@ -1,12 +1,15 @@
<template>
<div class="w-[80px] h-full box-border pt-[12px] pb-[12px] flex flex-col items-center justify-center">
<div :class="['flex flex-col gap-[16px]', {'mt-auto mb-[8px] shrink-1': item.id === 7}]" v-for="(item) in menus" :key="item.id">
<div class="w-[80px] h-full box-border flex flex-col items-center justify-center">
<div :class="['flex flex-col gap-[16px]', { 'mt-auto mb-[8px] shrink-1': item.id === 7 }]"
v-for="(item) in menus" :key="item.id">
<div :class="['cursor-pointer flex flex-col items-center justify-center']" @click="handleClick(item)">
<div :class="['box-border rounded-[16px] p-[8px]', {'bg-white': item.id === currentId}]">
<component :is="item.icon" :color="item.id === currentId ? item.activeColor : item.color" :class="['w-[32px] h-[32px]']" />
<div :class="['box-border rounded-[16px] p-[8px]', { 'bg-white': item.id === currentId }]">
<component :is="item.icon" :color="item.id === currentId ? item.activeColor : item.color"
:class="['w-[32px] h-[32px]']" />
</div>
<div :class="['text-[14px] mt-[4px] mb-[8px]', item.id === currentId ? `text-[${item.activeColor}]` : item.color]">
<div
:class="['text-[14px] mt-[4px] mb-[8px]', item.id === currentId ? `text-[${item.activeColor}]` : item.color]">
{{ item.name }}
</div>
</div>
@@ -20,10 +23,15 @@
<script setup lang="ts">
import { ref } from 'vue'
import { menus } from '@constant/menus'
import { useRouter } from "vue-router";
const currentId = ref(null)
const handleClick = (item: any) => {
const router = useRouter();
const currentId = ref(1)
const handleClick = async (item: any) => {
console.log("🚀 ~ handleClick ~ item:", item)
currentId.value = item.id
router.push(item.url);
}
</script>

View File

@@ -1,9 +1,59 @@
<template>
<div class="task-list w-[392px] h-full rounded-[16px] bg-white">
<slot></slot>
<div class="task p-[12px]">
<div class="flex border border-[#BEDBFF] h-[48px] p-[4px] rounded-[10px] bg-[#EFF6FF] task-tab">
<div v-for="item in tabs" :key="item.value"
class="flex-1 flex text-center items-center h-full align-middle text"
:class="active === item.value && 'active'" @click="changeTab(item.value)">
<div class="flex-1">{{ item.name }}<span v-if="item.total">{{ `${item.total > 98 && item.total +
'+'
|| item.total}` }}</span></div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts"></script>
<script setup lang="ts">
import { ref, reactive } from "vue";
<style></style>
const tabs = reactive([{
name: '待处理',
value: 1,
total: 10,
}, {
name: '已处理',
value: 2,
total: 99,
}])
const active = ref(1);
const changeTab = (val: number) => {
active.value = val;
};
</script>
<style scoped>
.task-tab .text {
color: #525866;
font-size: 14px;
cursor: pointer;
}
.task-tab .active {
position: relative;
color: #2B7FFF;
background: #FFFFFF;
border-radius: 8px;
}
.task-tab .active::after {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
content: '';
border-radius: 8px;
border: 1px solid #2B7FFF;
}
</style>

View File

@@ -1,14 +1,25 @@
<template>
<div class="box-border border-b-[1px] border-b-[#E5E8EE] mb-[20px] pb-[20px]">
<span class="text-[24px] font-500 text-[#171717] leading-[32px] mr-[8px]">
评价
{{ title }}
</span>
<span class="text-[12px] font-400 text-[#99A0AE] leading-[16px]">
评价数据智能整理精准优化服务
{{ desc }}
</span>
</div>
</template>
<script></script>
<script setup lang="ts">
import { defineProps } from 'vue'
<style></style>
const props = defineProps({
title: {
type: String,
default: ''
},
desc: {
type: String,
default: ''
}
})
</script>

View File

@@ -0,0 +1,46 @@
import pms from '@assets/images/channel/pms.png'
import xc from '@assets/images/channel/xc.png'
import qne from '@assets/images/channel/qne.png'
import fz from '@assets/images/channel/fz.png'
import mt from '@assets/images/channel/mt.png'
import dy from '@assets/images/channel/dy.png'
// 菜单列表申明
interface Item {
id: number
name: string
icon: any
}
export const channel: Item[] = [
{
id: 1,
name: 'PMS',
icon: pms,
},
{
id: 2,
name: '携程',
icon: xc,
},
{
id: 3,
name: '去哪儿',
icon: qne,
},
{
id: 4,
name: '飞猪',
icon: fz,
},
{
id: 5,
name: '美团',
icon: mt,
},
{
id: 6,
name: '抖音',
icon: dy,
}
]

View File

@@ -7,6 +7,7 @@ export interface MenuItem {
icon: any
color: string
activeColor: string
url: string
}
export const menus: MenuItem[] = [
@@ -16,6 +17,7 @@ export const menus: MenuItem[] = [
icon: RiHomeLine,
color: '#525866',
activeColor: '#2B7FFF',
url: '/home',
},
{
id: 2,
@@ -23,6 +25,7 @@ export const menus: MenuItem[] = [
icon: RiFileListLine,
color: '#525866',
activeColor: '#2B7FFF',
url: '/order',
},
{
id: 3,
@@ -30,6 +33,7 @@ export const menus: MenuItem[] = [
icon: RiHotelLine,
color: '#525866',
activeColor: '#2B7FFF',
url: '/stock',
},
{
id: 4,
@@ -37,6 +41,7 @@ export const menus: MenuItem[] = [
icon: RiChatQuoteLine,
color: '#525866',
activeColor: '#2B7FFF',
url: '/rate',
},
{
id: 5,
@@ -44,6 +49,7 @@ export const menus: MenuItem[] = [
icon: RiBarChartBoxAiLine,
color: '#525866',
activeColor: '#2B7FFF',
url: '/dashboard',
},
{
id: 6,
@@ -51,6 +57,7 @@ export const menus: MenuItem[] = [
icon: RiMoreLine,
color: '#525866',
activeColor: '#2B7FFF',
url: '/more',
},
{
id: 7,
@@ -58,5 +65,6 @@ export const menus: MenuItem[] = [
icon: RiSettingsLine,
color: '#525866',
activeColor: '#2B7FFF',
url: '/setting',
},
]

View File

@@ -0,0 +1,46 @@
import { RiUserLine, RiHotelLine, RiHotelBedLine, RiSettingsLine } from '@remixicon/vue'
// 菜单列表申明
export interface MenuItem {
id: number
name: string
icon: any
color: string
activeColor: string
componentName: string
}
export const systemMenus: MenuItem[] = [
{
id: 1,
name: '账号',
icon: RiUserLine,
color: '#525866',
activeColor: '#2B7FFF',
componentName: 'AccountSetting',
},
{
id: 2,
name: '渠道管理',
icon: RiHotelLine,
color: '#525866',
activeColor: '#2B7FFF',
componentName: 'ChannelSetting',
},
{
id: 3,
name: '房型管理',
icon: RiHotelBedLine,
color: '#525866',
activeColor: '#2B7FFF',
componentName: 'RoomTypeSetting',
},
{
id: 4,
name: '通用设置',
icon: RiSettingsLine,
color: '#525866',
activeColor: '#2B7FFF',
componentName: 'Version',
},
]

View File

@@ -1,12 +1,18 @@
<template>
<div class="bg box-border w-full h-screen flex pt-[8px] pb-[8px] pl-[8px]">
<div class="bg box-border w-full h-screen flex pt-[8px] pb-[8px] pl-[8px] ">
<div class="flex-1 flex gap-[8px]">
<div class="flex-1 h-full">
<slot></slot>
<router-view v-slot="{ Component, route }">
<transition name="fade-transform" mode="out-in">
<keep-alive>
<component :is="Component" :key="route.path" />
</keep-alive>
</transition>
</router-view>
</div>
<TaskList>
<slot name="task"></slot>
</TaskList>
<TaskList />
</div>
<Menus />
</div>

View File

@@ -3,7 +3,6 @@ import { createApp } from "vue";
import { createPinia } from "pinia";
import router from "./router";
import App from "./App.vue";
import Layout from "./layout/index.vue";
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import locale from 'element-plus/es/locale/lang/zh-cn'
@@ -14,8 +13,6 @@ const app = createApp(App);
// 使用 Pinia 状态管理
app.use(createPinia());
// 注册 Layout 组件
app.component('Layout', Layout);
// 使用 Vue Router
app.use(router);

View File

@@ -1,4 +1,5 @@
import { createRouter, createWebHistory } from "vue-router";
import Layout from '@/layout/index.vue'
const routes = [
{
@@ -9,21 +10,57 @@ const routes = [
{
path: "/browser",
name: "Browser",
component: () => import("@/views/browser/BrowserLayout.vue"),
component: () => import("@/browser/BrowserLayout.vue"),
meta: { requiresAuth: true },
},
{
path: "/home",
name: "Home",
path: "/",
component: Layout,
children: [
{
path: "home",
component: () => import("@/views/home/index.vue"),
name: "Home",
meta: { requiresAuth: true },
},
{
path: "/rate",
path: "stock",
name: "Stock",
component: () => import("@/views/stock/index.vue"),
meta: { requiresAuth: true },
},
{
path: "rate",
name: "Rate",
component: () => import("@/views/rate/index.vue"),
meta: { requiresAuth: true },
},
{
path: "order",
name: "Order",
component: () => import("@/views/order/index.vue"),
meta: { requiresAuth: true },
},
{
path: "more",
name: "More",
component: () => import("@/views/more/index.vue"),
meta: { requiresAuth: true },
},
{
path: "setting",
name: "Setting",
component: () => import("@/views/setting/index.vue"),
meta: { requiresAuth: true },
},
{
path: "/dashboard",
name: "Dashboard",
component: () => import("@/views/dashboard/index.vue"),
meta: { requiresAuth: true },
},
]
},
{
path: "/about",
name: "About",
@@ -34,6 +71,13 @@ const routes = [
const router = createRouter({
history: createWebHistory(),
routes,
scrollBehavior(to: any, from: any, savedPosition: any) {
if (savedPosition) {
return savedPosition
}
return { top: 0 }
},
});
router.beforeEach((to: any, from: any, next: any) => {
@@ -43,8 +87,13 @@ router.beforeEach((to: any, from: any, next: any) => {
return;
}
if (token && to.path !== "/rate") {
next({ path: "/rate" });
if (token && to.path === "/login") {
next({ path: "/home" });
return;
}
if (token && to.path === "/") {
next({ path: "/home" });
return;
}

View File

@@ -0,0 +1,7 @@
<template>
<div>看板</div>
</template>
<script setup lang="ts">
</script>

View File

@@ -1,18 +1,9 @@
<template>
<Layout>
<template #task>
<Task />
</template>
</Layout>
<div>
首页
</div>
</template>
<script setup lang="ts">
import Task from '../components/Task/index.vue'
const openBaidu = () => {
(window as any).ipcAPI?.openBaidu()
// 发送日志
(window as any).ipcAPI?.logToMain('info', '打开百度')
}
</script>

View File

@@ -1,13 +1,12 @@
<template>
<div
class="h-screen box-border p-[8px] login-bg flex items-center justify-center"
>
<div class="h-screen box-border p-[8px] login-bg flex items-center justify-center">
<div class="w-[836px] h-full bg-white rounded-2xl p-[32px] flex flex-col">
<div class="flex items-center">
<img class="w-[48px] h-[48px]" src="@assets/images/login/blue_logo.png" />
<span class="ml-auto text-[14px] text-gray-600">没有账号</span>
<button class="bg-sky-50 rounded-[8px] text-[14px] text-sky-600 px-[12px] py-[6px] focus-visible:outline-none">注册</button>
<button
class="bg-sky-50 rounded-[8px] text-[14px] text-sky-600 px-[12px] py-[6px] focus-visible:outline-none">注册</button>
</div>
<div class="flex flex-col items-center justify-center mb-[24px] box-border pt-[108px]">
@@ -20,37 +19,22 @@
<div class="font-[14px] text-gray-700 mb-2">账号</div>
<div class="border rounded-[10px] flex items-center gap-2 box-border px-[12px] py-[10px]">
<RiUser3Fill size="20px" color="#99A0AE" />
<input
class="flex-1 focus-visible:outline-none"
type="text"
v-model.trim="form.account"
placeholder="请输入账号"
@keyup.enter="onSubmit"
/>
<input class="flex-1 focus-visible:outline-none" type="text" v-model.trim="form.account" placeholder="请输入账号"
@keyup.enter="onSubmit" />
</div>
<p v-if="errors.account" class="mt-1 text-xs text-red-500">{{ errors.account }}</p>
<div class="font-[14px] text-gray-700 mb-[8px] mt-[12px]">密码</div>
<div class="flex items-center gap-2 border rounded-[10px] box-border px-[12px] py-[10px]">
<RiKey2Fill size="20px" color="#99A0AE" />
<input
class="flex-1 focus-visible:outline-none"
:type="showPwd ? 'text' : 'password'"
v-model.trim="form.password"
placeholder="请输入密码"
@keyup.enter="onSubmit"
/>
<input class="flex-1 focus-visible:outline-none" :type="showPwd ? 'text' : 'password'"
v-model.trim="form.password" placeholder="请输入密码" @keyup.enter="onSubmit" />
</div>
<p v-if="errors.password" class="mt-1 text-xs text-red-500">{{ errors.password }}</p>
<!-- 验证码 -->
<div class="font-[14px] text-gray-700 mb-[8px] mt-[12px]">验证码</div>
<div class="flex items-center gap-2 border rounded-[10px] box-border px-[12px] py-[10px]">
<input
class="flex-1 focus-visible:outline-none"
type="text"
v-model.trim="form.code"
placeholder="请输入验证码"
@keyup.enter="onSubmit"
/>
<input class="flex-1 focus-visible:outline-none" type="text" v-model.trim="form.code" placeholder="请输入验证码"
@keyup.enter="onSubmit" />
<img class="w-[80px] h-[40px]" src="" />
</div>
<p v-if="errors.code" class="mt-1 text-xs text-red-500">{{ errors.code }}</p>
@@ -58,31 +42,21 @@
<!-- 记住密码|忘记密码 -->
<div class="flex items-center justify-between mb-[24px] mt-[24px]">
<div class="flex items-center gap-2">
<input
type="checkbox"
v-model="showPwd"
class="w-[14px] h-[14px] rounded-[4px]"
/>
<input type="checkbox" v-model="showPwd" class="w-[14px] h-[14px] rounded-[4px]" />
<span class="text-[14px] text-gray-600">记住密码</span>
</div>
<span class="text-[14px] text-sky-600">忘记密码</span>
</div>
<!-- 登录按钮 -->
<button
class="w-full py-2 bg-blue-600 text-white rounded-[8px] hover:bg-blue-700 disabled:bg-blue-300"
@click="onSubmit"
>
<button class="w-full py-2 bg-blue-600 text-white rounded-[8px] hover:bg-blue-700 disabled:bg-blue-300"
@click="onSubmit">
{{ loading ? '登录中' : '登录' }}
</button>
<!-- 同意协议 -->
<div class="flex items-center justify-center gap-2 mt-[24px]">
<input
type="checkbox"
v-model="form.agreement"
class="w-[14px] h-[14px] rounded-[4px]"
/>
<input type="checkbox" v-model="form.agreement" class="w-[14px] h-[14px] rounded-[4px]" />
<span class="text-[14px] text-gray-600">我已同意</span>
<span class="text-[14px] text-sky-600">使用协议</span>
<span class="text-[14px] text-gray-600"></span>
@@ -103,8 +77,8 @@
<script setup lang="ts">
import { ref, reactive } from "vue";
import { useRouter } from "vue-router";
import { login as apiLogin } from "@/renderer/api/login";
import { RiUser3Fill , RiKey2Fill} from '@remixicon/vue'
// import { login as apiLogin } from "@/renderer/api/login";
import { RiUser3Fill, RiKey2Fill } from '@remixicon/vue'
const router = useRouter();
const form = reactive({ account: "", password: "", agreement: "", code: "" });
@@ -131,7 +105,8 @@ const onSubmit = async () => {
// const token = res && (res.token || res.data?.token || res.access_token);
// if (!token) throw new Error("登录失败");
// localStorage.setItem("token", token);
await (window as any).ipcAPI.app.setFrameless('/home')
// await (window as any).ipcAPI.app.setFrameless('/home')
router.push('/home');
} finally {
// loading.value = false;
}

View File

@@ -0,0 +1,7 @@
<template>
<div>更多</div>
</template>
<script setup lang="ts">
</script>

View File

@@ -0,0 +1,7 @@
<template>
<div>订单</div>
</template>
<script setup lang="ts">
</script>

View File

@@ -1,16 +1,13 @@
<template>
<Layout>
<div class="bg-white box-border w-[936px] h-full rounded-[16px] p-[20px]">
<RateTitleSection />
<div class="bg-white box-border w-full h-full rounded-[16px] p-[20px]">
<TitleSection title="评价" desc="评价数据智能整理,精准优化服务" />
<RatePanelSection />
<RateContentSection />
</div>
</Layout>
</template>
<script setup lang="ts" name="Rate">
import RateTitleSection from './components/RateTitleSection/index.vue'
import TitleSection from './components/TitleSection/index.vue'
import RatePanelSection from './components/RatePanelSection/index.vue'
import RateContentSection from './components/RateContentSection/index.vue'
</script>

View File

@@ -0,0 +1,29 @@
<template>
<div class="flex-1 h-full p-[20px] select-none">
<TitleSection title="账号设置" desc="请关联PMS和渠道房型名称可使用智能对标" />
<div
class="w-full flex items-center mt-[20px] py-[20px] box-border border-b-[1px] border-dashed border-b-[#E5E8EE]">
<div class="label w-[64px] text-[16px] font-medium text-[#171717] mr-[24px]">账号</div>
<div class="value text-[14px] font-medium text-[#171717]">1234567890</div>
</div>
<div class="w-full flex items-center py-[20px] box-border border-b-[1px] border-dashed border-b-[#E5E8EE]">
<div class="label w-[64px] text-[16px] font-medium text-[#171717] mr-[24px]">登录密码</div>
<div class="value text-[14px] text-[#99A0AE]">保障投资者登录操作时使用上次登录时间2022-11-09 16:24:30</div>
<div class="border-[1px] border-[#E5E8EE] rounded-[6px] px-[6px] py-[4px] flex items-center ml-[24px]">
<RiCheckboxCircleFill class="w-[16px] h-[16px]" color="#1FC16B" />
<span class="text-[12px] text-[#525866] ml-[2px]">已设置</span>
</div>
<el-button type="text" class="ml-auto">修改密码</el-button>
</div>
</div>
</template>
<script setup lang="ts">
import { RiCheckboxCircleFill } from '@remixicon/vue'
import TitleSection from '@/components/TitleSection/index.vue'
</script>

View File

@@ -0,0 +1,36 @@
<template>
<div class="flex-1 h-full p-[20px] select-none">
<TitleSection title="渠道管理" desc="绑定酒店使用的相关渠道的账户和密码用于智能操作" />
<div class="grid grid-cols-3 gap-[12px] mb-[12px] select-none">
<div class="border-[1px] border-[#E5E8ED] box-border flex flex-col rounded-[12px] overflow-hidden"
v-for="item in channel" :key="item.id">
<div class="bg-[#E0E0E0] h-[120px]"></div>
<div class="flex items-center relative mt-[-20px] pl-[12px]">
<img :src="item.icon" class="w-[40px] h-[40px]">
</div>
<div class="flex items-center p-[12px]">
<span class="text-[14px] font-500 text-[#171717] leading-[20px]">
{{ item.name }}
</span>
<div class="bg-[#F2F5F8] rounded-[6px] flex items-center ml-[8px] py-[4px] px-[6px]">
<RiForbidLine class="w-[16px] h-[16px]" color="#717784" />
<span class="text-[12px] font-500 text-[#717784] leading-[20px] ml-[4px]">
未绑定
</span>
</div>
</div>
<div class="p-[12px] border-t-[1px] border-t-[#E5E8ED]">
<el-button plain>绑定</el-button>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { channel } from '@constant/channel'
import { RiForbidLine } from '@remixicon/vue'
import TitleSection from '@/components/TitleSection/index.vue'
</script>

View File

@@ -0,0 +1,3 @@
<template></template>
<script setup lang="ts"></script>

View File

@@ -0,0 +1,27 @@
<template>
<div
class="w-[136px] h-full box-border border-r-[1px] border-r-[#E5E8EE] py-[12px] px-[8px] flex flex-col gap-[4px] select-none">
<div class="text-[12px] text-[#99A0AE] p-[4px]">系统设置</div>
<div
:class="['box-border flex items-center py-[10px] px-[12px] rounded-[6px] cursor-pointer', item.id === currentId ? 'bg-[#EFF6FF]' : '']"
v-for="item in systemMenus" :key="item.id" @click="handleClick(item)">
<component :is="item.icon" :color="item.id === currentId ? item.activeColor : item.color"
class="w-[20px] h-[20px]" />
<span class="box-border px-[8px] text-[14px] font-medium text-[#525866]">{{ item.name }}</span>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, defineEmits } from 'vue'
import { systemMenus } from '@/constant/system-config'
const currentId = ref(1)
const emits = defineEmits(['change'])
const handleClick = async (item: any) => {
currentId.value = item.id
emits('change', item)
}
</script>

View File

@@ -0,0 +1,16 @@
<template>
<div class="flex-1 h-full p-[20px] select-none">
<TitleSection title="账号设置" desc="请关联PMS和渠道房型名称可使用智能对标" />
<div
class="w-full flex items-center mt-[20px] py-[20px] box-border border-b-[1px] border-dashed border-b-[#E5E8EE]">
<div class="label w-[64px] text-[16px] font-medium text-[#171717] mr-[24px]">当前版本</div>
<div class="value text-[16px] font-medium text-[#171717]">1.0.0</div>
<el-button type="text" class="ml-auto">检查更新</el-button>
</div>
</div>
</template>
<script setup lang="ts">
import TitleSection from '@/components/TitleSection/index.vue'
</script>

View File

@@ -0,0 +1,25 @@
<template>
<div class="bg-white box-border w-full h-full rounded-[16px] flex">
<SystemConfig @change=onChange />
<component :is="currentComponent" />
</div>
</template>
<script setup lang="ts">
import { shallowRef } from 'vue'
import SystemConfig from './components/SystemConfig/index.vue'
import AccountSetting from './components/AccountSetting/index.vue'
import Version from './components/Version/index.vue'
import ChannelSetting from './components/ChannelSetting/index.vue'
import RoomTypeSetting from './components/RoomTypeSetting/index.vue'
const currentComponent = shallowRef(AccountSetting)
const components: any = {
AccountSetting,
ChannelSetting,
RoomTypeSetting,
Version,
}
const onChange = ({ componentName }: any) => currentComponent.value = components[componentName]
</script>

View File

@@ -0,0 +1,7 @@
<template>
<div>订单</div>
</template>
<script setup lang="ts">
</script>

View File

@@ -52,5 +52,32 @@ export interface IPCAPI {
invokeAsync<T extends keyof IPCTypings>(channel: T, ...args: IPCTypings[T]['params']): IPCTypings[T]['return'],
on<T extends keyof IPCTypings>(channel: T, callback: (...args: IPCTypings[T]['params']) => void): () => void
send<T extends keyof IPCTypings>(channel: T, ...args: IPCTypings[T]['params']): void,
getCurrentWindowId(): number
getCurrentWindowId(): number,
versions: NodeJS.ProcessVersions,
external: {
open: (url: string) => void
},
window: {
minimize: () => void,
maximize: () => void,
close: () => void
},
app: {
setFrameless: (route?: string) => void
},
tabs: {
create: (url?: string) => void,
list: () => void,
navigate: (tabId: string, url: string) => void,
reload: (tabId: string) => void,
back: (tabId: string) => void,
forward: (tabId: string) => void,
switch: (tabId: string) => void,
close: (tabId: string) => void,
on: (event: 'tab-updated' | 'tab-created' | 'tab-closed' | 'tab-switched', handler: (payload: any) => void) => void
},
readFile: (filePath: string) => Promise<{success: boolean, data?: string, error?: string}>,
logToMain: (logLevel: string, message: string) => void,
// 打开新窗口
openNewTab: (url: string) => Promise<void>,
}

View File

@@ -23,6 +23,7 @@
"@api/*": ["src/renderer/api/*"],
"@/types": ["src/renderer/types/index.ts"],
"@modules/*": ["src/electron/main/modules/*"],
"@/shared/*": ["src/shared/*"],
},
"outDir": "dist",
"moduleResolution": "bundler",