85 changed files with 4908 additions and 4900 deletions
@ -1,32 +1,32 @@ |
|||
{ |
|||
"tabWidth": 2, |
|||
"useTabs": false, |
|||
"semi": false, |
|||
"singleQuote": false, |
|||
"trailingComma": "all", |
|||
"bracketSpacing": true, |
|||
"arrowParens": "avoid", |
|||
"printWidth": 140, |
|||
"htmlWhitespaceSensitivity": "ignore", |
|||
"proseWrap": "preserve", |
|||
"endOfLine": "auto", |
|||
"vueIndentScriptAndStyle": true, |
|||
"embeddedLanguageFormatting": "auto", |
|||
"jsxSingleQuote": false, |
|||
"jsxBracketSameLine": false, |
|||
"quoteProps": "as-needed", |
|||
"overrides": [ |
|||
{ |
|||
"files": "*.json", |
|||
"options": { |
|||
"printWidth": 80 |
|||
} |
|||
}, |
|||
{ |
|||
"files": ["*.vue", "*.tsx"], |
|||
"options": { |
|||
"singleAttributePerLine": false |
|||
} |
|||
} |
|||
] |
|||
"tabWidth": 2, |
|||
"useTabs": false, |
|||
"semi": false, |
|||
"singleQuote": false, |
|||
"trailingComma": "all", |
|||
"bracketSpacing": true, |
|||
"arrowParens": "avoid", |
|||
"printWidth": 140, |
|||
"htmlWhitespaceSensitivity": "ignore", |
|||
"proseWrap": "preserve", |
|||
"endOfLine": "auto", |
|||
"vueIndentScriptAndStyle": true, |
|||
"embeddedLanguageFormatting": "auto", |
|||
"jsxSingleQuote": false, |
|||
"jsxBracketSameLine": false, |
|||
"quoteProps": "as-needed", |
|||
"overrides": [ |
|||
{ |
|||
"files": "*.json", |
|||
"options": { |
|||
"printWidth": 80 |
|||
} |
|||
}, |
|||
{ |
|||
"files": ["*.vue", "*.tsx"], |
|||
"options": { |
|||
"singleAttributePerLine": false |
|||
} |
|||
} |
|||
] |
|||
} |
|||
|
@ -1,3 +1,3 @@ |
|||
{ |
|||
"recommendations": ["dbaeumer.vscode-eslint", "lokalise.i18n-ally"] |
|||
"recommendations": ["dbaeumer.vscode-eslint", "lokalise.i18n-ally"] |
|||
} |
|||
|
@ -1,39 +1,39 @@ |
|||
{ |
|||
"version": "0.2.0", |
|||
"configurations": [ |
|||
{ |
|||
"name": "Debug Main Process", |
|||
"type": "node", |
|||
"request": "launch", |
|||
"cwd": "${workspaceRoot}", |
|||
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite", |
|||
"windows": { |
|||
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite.cmd" |
|||
}, |
|||
"runtimeArgs": ["--sourcemap"], |
|||
"env": { |
|||
"REMOTE_DEBUGGING_PORT": "9222" |
|||
} |
|||
}, |
|||
{ |
|||
"name": "Debug Renderer Process", |
|||
"port": 9222, |
|||
"request": "attach", |
|||
"type": "chrome", |
|||
"webRoot": "${workspaceFolder}/src/renderer", |
|||
"timeout": 60000, |
|||
"presentation": { |
|||
"hidden": true |
|||
} |
|||
} |
|||
], |
|||
"compounds": [ |
|||
{ |
|||
"name": "Debug All", |
|||
"configurations": ["Debug Main Process", "Debug Renderer Process"], |
|||
"presentation": { |
|||
"order": 1 |
|||
} |
|||
} |
|||
] |
|||
"version": "0.2.0", |
|||
"configurations": [ |
|||
{ |
|||
"name": "Debug Main Process", |
|||
"type": "node", |
|||
"request": "launch", |
|||
"cwd": "${workspaceRoot}", |
|||
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite", |
|||
"windows": { |
|||
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite.cmd" |
|||
}, |
|||
"runtimeArgs": ["--sourcemap"], |
|||
"env": { |
|||
"REMOTE_DEBUGGING_PORT": "9222" |
|||
} |
|||
}, |
|||
{ |
|||
"name": "Debug Renderer Process", |
|||
"port": 9222, |
|||
"request": "attach", |
|||
"type": "chrome", |
|||
"webRoot": "${workspaceFolder}/src/renderer", |
|||
"timeout": 60000, |
|||
"presentation": { |
|||
"hidden": true |
|||
} |
|||
} |
|||
], |
|||
"compounds": [ |
|||
{ |
|||
"name": "Debug All", |
|||
"configurations": ["Debug Main Process", "Debug Renderer Process"], |
|||
"presentation": { |
|||
"order": 1 |
|||
} |
|||
} |
|||
] |
|||
} |
|||
|
@ -1,18 +1,18 @@ |
|||
{ |
|||
"[typescript]": { |
|||
"editor.defaultFormatter": "esbenp.prettier-vscode" |
|||
}, |
|||
"[javascript]": { |
|||
"editor.defaultFormatter": "esbenp.prettier-vscode" |
|||
}, |
|||
"[json]": { |
|||
"editor.defaultFormatter": "esbenp.prettier-vscode" |
|||
}, |
|||
"i18n-ally.localesPaths": ["packages/locales/languages"], |
|||
"i18n-ally.sourceLanguage": "zh", |
|||
"i18n-ally.displayLanguage": "zh", |
|||
"i18n-ally.keystyle": "nested", |
|||
"i18n-ally.extract.autoDetect": true, |
|||
"i18n-ally.enabledFrameworks": ["vue"], |
|||
"i18n-ally.enabledParsers": ["json"] |
|||
"[typescript]": { |
|||
"editor.defaultFormatter": "esbenp.prettier-vscode" |
|||
}, |
|||
"[javascript]": { |
|||
"editor.defaultFormatter": "esbenp.prettier-vscode" |
|||
}, |
|||
"[json]": { |
|||
"editor.defaultFormatter": "esbenp.prettier-vscode" |
|||
}, |
|||
"i18n-ally.localesPaths": ["packages/locales/languages"], |
|||
"i18n-ally.sourceLanguage": "zh", |
|||
"i18n-ally.displayLanguage": "zh", |
|||
"i18n-ally.keystyle": "nested", |
|||
"i18n-ally.extract.autoDetect": true, |
|||
"i18n-ally.enabledFrameworks": ["vue"], |
|||
"i18n-ally.enabledParsers": ["json"] |
|||
} |
|||
|
@ -1,45 +1,45 @@ |
|||
appId: com.zephyr.app |
|||
productName: zephyr |
|||
directories: |
|||
buildResources: build |
|||
buildResources: build |
|||
files: |
|||
- "!**/.vscode/*" |
|||
- "!src/*" |
|||
- "!electron.vite.config.{js,ts,mjs,cjs}" |
|||
- "!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}" |
|||
- "!{.env,.env.*,.npmrc,pnpm-lock.yaml}" |
|||
- "!{tsconfig.json,tsconfig.node.json,tsconfig.web.json}" |
|||
- "!**/.vscode/*" |
|||
- "!src/*" |
|||
- "!electron.vite.config.{js,ts,mjs,cjs}" |
|||
- "!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}" |
|||
- "!{.env,.env.*,.npmrc,pnpm-lock.yaml}" |
|||
- "!{tsconfig.json,tsconfig.node.json,tsconfig.web.json}" |
|||
asarUnpack: |
|||
- resources/** |
|||
- resources/** |
|||
win: |
|||
executableName: zephyr |
|||
executableName: zephyr |
|||
nsis: |
|||
artifactName: ${name}-${version}-setup.${ext} |
|||
shortcutName: ${productName} |
|||
uninstallDisplayName: ${productName} |
|||
createDesktopShortcut: always |
|||
artifactName: ${name}-${version}-setup.${ext} |
|||
shortcutName: ${productName} |
|||
uninstallDisplayName: ${productName} |
|||
createDesktopShortcut: always |
|||
mac: |
|||
entitlementsInherit: build/entitlements.mac.plist |
|||
extendInfo: |
|||
- NSCameraUsageDescription: Application requests access to the device's camera. |
|||
- NSMicrophoneUsageDescription: Application requests access to the device's microphone. |
|||
- NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder. |
|||
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder. |
|||
notarize: false |
|||
entitlementsInherit: build/entitlements.mac.plist |
|||
extendInfo: |
|||
- NSCameraUsageDescription: Application requests access to the device's camera. |
|||
- NSMicrophoneUsageDescription: Application requests access to the device's microphone. |
|||
- NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder. |
|||
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder. |
|||
notarize: false |
|||
dmg: |
|||
artifactName: ${name}-${version}.${ext} |
|||
artifactName: ${name}-${version}.${ext} |
|||
linux: |
|||
target: |
|||
- AppImage |
|||
- snap |
|||
- deb |
|||
maintainer: electronjs.org |
|||
category: Utility |
|||
target: |
|||
- AppImage |
|||
- snap |
|||
- deb |
|||
maintainer: electronjs.org |
|||
category: Utility |
|||
appImage: |
|||
artifactName: ${name}-${version}.${ext} |
|||
artifactName: ${name}-${version}.${ext} |
|||
npmRebuild: false |
|||
publish: |
|||
provider: generic |
|||
url: https://example.com/auto-updates |
|||
provider: generic |
|||
url: https://example.com/auto-updates |
|||
electronDownload: |
|||
mirror: https://npmmirror.com/mirrors/electron/ |
|||
mirror: https://npmmirror.com/mirrors/electron/ |
|||
|
@ -1,78 +1,78 @@ |
|||
{ |
|||
"name": "zephyr", |
|||
"type": "module", |
|||
"private": true, |
|||
"version": "0.0.1", |
|||
"description": "An Electron application with Vue and TypeScript", |
|||
"main": "./out/main/index.js", |
|||
"author": "example.com", |
|||
"homepage": "https://electron-vite.org", |
|||
"scripts": { |
|||
"runInstall": "node node_modules/electron/install.js", |
|||
"format": "prettier --write .", |
|||
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts,.vue --fix", |
|||
"typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false", |
|||
"typecheck:web": "vue-tsc --noEmit -p tsconfig.web.json --composite false", |
|||
"typecheck": "npm run typecheck:node && npm run typecheck:web", |
|||
"start": "electron-vite preview", |
|||
"dev": "chcp 65001 && set DEBUG=app:*&& electron-vite dev", |
|||
"dev:watch": "chcp 65001 & set DEBUG=app:*& electron-vite dev --watch", |
|||
"build": "npm run typecheck && electron-vite build", |
|||
"postinstall": "electron-builder install-app-deps", |
|||
"build:unpack": "npm run build && electron-builder --dir", |
|||
"build:win": "npm run build && electron-builder --win", |
|||
"build:mac": "npm run build && electron-builder --mac", |
|||
"build:linux": "npm run build && electron-builder --linux" |
|||
}, |
|||
"dependencies": { |
|||
"@electron-toolkit/preload": "^3.0.1", |
|||
"@electron-toolkit/utils": "^3.0.0", |
|||
"electron-updater": "^6.3.9", |
|||
"inversify": "^6.2.2", |
|||
"lowdb": "^7.0.1", |
|||
"reflect-metadata": "^0.2.2" |
|||
}, |
|||
"devDependencies": { |
|||
"@electron-toolkit/eslint-config": "^1.0.2", |
|||
"@electron-toolkit/eslint-config-ts": "^2.0.0", |
|||
"@electron-toolkit/tsconfig": "^1.0.1", |
|||
"@intlify/unplugin-vue-i18n": "^6.0.3", |
|||
"@rushstack/eslint-patch": "^1.10.5", |
|||
"@types/debug": "^4.1.12", |
|||
"@types/node": "^20.17.19", |
|||
"@unocss/preset-rem-to-px": "^0.64.1", |
|||
"@unocss/reset": "^0.64.1", |
|||
"@vitejs/plugin-vue": "^5.2.1", |
|||
"@vitejs/plugin-vue-jsx": "^4.1.1", |
|||
"@vue/eslint-config-prettier": "^9.0.0", |
|||
"@vue/eslint-config-typescript": "^13.0.0", |
|||
"@vueuse/core": "^12.7.0", |
|||
"debug": "^4.4.0", |
|||
"electron": "^31.7.7", |
|||
"electron-builder": "^24.13.3", |
|||
"electron-vite": "^2.3.0", |
|||
"eslint": "^8.57.1", |
|||
"eslint-plugin-vue": "^9.32.0", |
|||
"extract-zip": "^2.0.1", |
|||
"locales": "workspace:*", |
|||
"lodash-es": "^4.17.21", |
|||
"monaco-editor": "^0.52.2", |
|||
"prettier": "^3.5.1", |
|||
"rotating-file-stream": "^3.2.6", |
|||
"sass": "^1.85.0", |
|||
"simplebar-vue": "^2.4.0", |
|||
"typescript": "^5.7.3", |
|||
"unocss": "^0.64.1", |
|||
"unplugin-auto-import": "^19.1.0", |
|||
"unplugin-vue-components": "^28.4.0", |
|||
"unplugin-vue-macros": "^2.14.2", |
|||
"unplugin-vue-router": "^0.11.2", |
|||
"vite": "^5.4.14", |
|||
"vite-plugin-monaco-editor": "^1.1.0", |
|||
"vite-plugin-vue-layouts": "^0.11.0", |
|||
"vue": "^3.5.13", |
|||
"vue-i18n": "^11.1.1", |
|||
"vue-router": "^4.5.0", |
|||
"vue-tsc": "^2.1.10" |
|||
} |
|||
"name": "zephyr", |
|||
"type": "module", |
|||
"private": true, |
|||
"version": "0.0.1", |
|||
"description": "An Electron application with Vue and TypeScript", |
|||
"main": "./out/main/index.js", |
|||
"author": "example.com", |
|||
"homepage": "https://electron-vite.org", |
|||
"scripts": { |
|||
"runInstall": "node node_modules/electron/install.js", |
|||
"format": "prettier --write .", |
|||
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts,.vue --fix", |
|||
"typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false", |
|||
"typecheck:web": "vue-tsc --noEmit -p tsconfig.web.json --composite false", |
|||
"typecheck": "npm run typecheck:node && npm run typecheck:web", |
|||
"start": "electron-vite preview", |
|||
"dev": "chcp 65001 && set DEBUG=app:*&& electron-vite dev", |
|||
"dev:watch": "chcp 65001 & set DEBUG=app:*& electron-vite dev --watch", |
|||
"build": "npm run typecheck && electron-vite build", |
|||
"postinstall": "electron-builder install-app-deps", |
|||
"build:unpack": "npm run build && electron-builder --dir", |
|||
"build:win": "npm run build && electron-builder --win", |
|||
"build:mac": "npm run build && electron-builder --mac", |
|||
"build:linux": "npm run build && electron-builder --linux" |
|||
}, |
|||
"dependencies": { |
|||
"@electron-toolkit/preload": "^3.0.1", |
|||
"@electron-toolkit/utils": "^3.0.0", |
|||
"electron-updater": "^6.3.9", |
|||
"inversify": "^6.2.2", |
|||
"lowdb": "^7.0.1", |
|||
"reflect-metadata": "^0.2.2" |
|||
}, |
|||
"devDependencies": { |
|||
"@electron-toolkit/eslint-config": "^1.0.2", |
|||
"@electron-toolkit/eslint-config-ts": "^2.0.0", |
|||
"@electron-toolkit/tsconfig": "^1.0.1", |
|||
"@intlify/unplugin-vue-i18n": "^6.0.3", |
|||
"@rushstack/eslint-patch": "^1.10.5", |
|||
"@types/debug": "^4.1.12", |
|||
"@types/node": "^20.17.19", |
|||
"@unocss/preset-rem-to-px": "^0.64.1", |
|||
"@unocss/reset": "^0.64.1", |
|||
"@vitejs/plugin-vue": "^5.2.1", |
|||
"@vitejs/plugin-vue-jsx": "^4.1.1", |
|||
"@vue/eslint-config-prettier": "^9.0.0", |
|||
"@vue/eslint-config-typescript": "^13.0.0", |
|||
"@vueuse/core": "^12.7.0", |
|||
"debug": "^4.4.0", |
|||
"electron": "^31.7.7", |
|||
"electron-builder": "^24.13.3", |
|||
"electron-vite": "^2.3.0", |
|||
"eslint": "^8.57.1", |
|||
"eslint-plugin-vue": "^9.32.0", |
|||
"extract-zip": "^2.0.1", |
|||
"locales": "workspace:*", |
|||
"lodash-es": "^4.17.21", |
|||
"monaco-editor": "^0.52.2", |
|||
"prettier": "^3.5.1", |
|||
"rotating-file-stream": "^3.2.6", |
|||
"sass": "^1.85.0", |
|||
"simplebar-vue": "^2.4.0", |
|||
"typescript": "^5.7.3", |
|||
"unocss": "^0.64.1", |
|||
"unplugin-auto-import": "^19.1.0", |
|||
"unplugin-vue-components": "^28.4.0", |
|||
"unplugin-vue-macros": "^2.14.2", |
|||
"unplugin-vue-router": "^0.11.2", |
|||
"vite": "^5.4.14", |
|||
"vite-plugin-monaco-editor": "^1.1.0", |
|||
"vite-plugin-vue-layouts": "^0.11.0", |
|||
"vue": "^3.5.13", |
|||
"vue-i18n": "^11.1.1", |
|||
"vue-router": "^4.5.0", |
|||
"vue-tsc": "^2.1.10" |
|||
} |
|||
} |
|||
|
@ -1,2 +1,2 @@ |
|||
packages: |
|||
- "packages/*" |
|||
- "packages/*" |
|||
|
@ -1,11 +1,12 @@ |
|||
<!DOCTYPE html> |
|||
<!doctype html> |
|||
<html lang="en"> |
|||
<head> |
|||
<meta charset="UTF-8"> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|||
<head> |
|||
<meta charset="UTF-8" /> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
|||
<title>Document</title> |
|||
</head> |
|||
<body> |
|||
前往 <a href="https://baidu.com" target="_blank">百度</a> |
|||
</body> |
|||
</head> |
|||
<body> |
|||
前往 |
|||
<a href="https://baidu.com" target="_blank">百度</a> |
|||
</body> |
|||
</html> |
|||
|
@ -1,54 +1,54 @@ |
|||
import { _Base } from "../../lib/_Base" |
|||
|
|||
export class Tabs extends _Base { |
|||
constructor() { |
|||
super() |
|||
} |
|||
|
|||
private isListen: boolean = false |
|||
|
|||
private execUpdate = (...args) => { |
|||
this.#fnList.forEach(v => v(...args)) |
|||
} |
|||
|
|||
#fnList: ((...args) => void)[] = [] |
|||
listenUpdate(cb: (...args) => void) { |
|||
if (!this.isListen) { |
|||
api.on("main:TabsCommand.update", this.execUpdate) |
|||
this.isListen = true |
|||
} |
|||
this.#fnList.push(cb) |
|||
} |
|||
|
|||
unListenUpdate(fn: (...args) => void) { |
|||
this.#fnList = this.#fnList.filter(v => { |
|||
return v !== fn |
|||
}) |
|||
if (!this.#fnList.length) { |
|||
api.off("main:TabsCommand.update", this.execUpdate) |
|||
this.isListen = false |
|||
} |
|||
} |
|||
|
|||
bindPosition(data) { |
|||
api.call("TabsCommand.bindElement", data) |
|||
} |
|||
|
|||
closeAll() { |
|||
api.call("TabsCommand.closeAll") |
|||
} |
|||
|
|||
sync() { |
|||
api.call("TabsCommand.sync") |
|||
} |
|||
|
|||
unListenerAll() { |
|||
this.#fnList = [] |
|||
api.offAll("main:TabsCommand.update") |
|||
} |
|||
|
|||
async getAllTabs() { |
|||
const res = await api.call("TabsCommand.getAllTabs") |
|||
return res |
|||
} |
|||
constructor() { |
|||
super() |
|||
} |
|||
|
|||
private isListen: boolean = false |
|||
|
|||
private execUpdate = (...args) => { |
|||
this.#fnList.forEach(v => v(...args)) |
|||
} |
|||
|
|||
#fnList: ((...args) => void)[] = [] |
|||
listenUpdate(cb: (...args) => void) { |
|||
if (!this.isListen) { |
|||
api.on("main:TabsCommand.update", this.execUpdate) |
|||
this.isListen = true |
|||
} |
|||
this.#fnList.push(cb) |
|||
} |
|||
|
|||
unListenUpdate(fn: (...args) => void) { |
|||
this.#fnList = this.#fnList.filter(v => { |
|||
return v !== fn |
|||
}) |
|||
if (!this.#fnList.length) { |
|||
api.off("main:TabsCommand.update", this.execUpdate) |
|||
this.isListen = false |
|||
} |
|||
} |
|||
|
|||
bindPosition(data) { |
|||
api.call("TabsCommand.bindElement", data) |
|||
} |
|||
|
|||
closeAll() { |
|||
api.call("TabsCommand.closeAll") |
|||
} |
|||
|
|||
sync() { |
|||
api.call("TabsCommand.sync") |
|||
} |
|||
|
|||
unListenerAll() { |
|||
this.#fnList = [] |
|||
api.offAll("main:TabsCommand.update") |
|||
} |
|||
|
|||
async getAllTabs() { |
|||
const res = await api.call("TabsCommand.getAllTabs") |
|||
return res |
|||
} |
|||
} |
|||
|
@ -1,12 +1,12 @@ |
|||
export abstract class _Base { |
|||
static instance |
|||
static instance |
|||
|
|||
static getInstance<T>(): T { |
|||
if (!this.instance) { |
|||
// 如果实例不存在,则创建一个新的实例
|
|||
// @ts-ignore ...
|
|||
this.instance = new this() |
|||
} |
|||
return this.instance |
|||
static getInstance<T>(): T { |
|||
if (!this.instance) { |
|||
// 如果实例不存在,则创建一个新的实例
|
|||
// @ts-ignore ...
|
|||
this.instance = new this() |
|||
} |
|||
return this.instance |
|||
} |
|||
} |
|||
|
@ -1,29 +1,29 @@ |
|||
import { IApiClient } from "./abstract" |
|||
|
|||
export class BrowserApiClient implements IApiClient { |
|||
call<T = any>(command: string, ...args: any[]): Promise<T> { |
|||
// 浏览器特定实现,可能使用 fetch 或其他方式
|
|||
const [service, method] = command.split(".") |
|||
return fetch(`/api/${service}/${method}`, { |
|||
method: "POST", |
|||
body: JSON.stringify(args), |
|||
headers: { "Content-Type": "application/json" }, |
|||
}).then(res => res.json()) |
|||
} |
|||
call<T = any>(command: string, ...args: any[]): Promise<T> { |
|||
// 浏览器特定实现,可能使用 fetch 或其他方式
|
|||
const [service, method] = command.split(".") |
|||
return fetch(`/api/${service}/${method}`, { |
|||
method: "POST", |
|||
body: JSON.stringify(args), |
|||
headers: { "Content-Type": "application/json" }, |
|||
}).then(res => res.json()) |
|||
} |
|||
|
|||
// 实现其他方法...
|
|||
on<K extends string>(channel: K, callback: (...args: any[]) => void): void { |
|||
// 浏览器中可能使用 WebSocket 或其他方式
|
|||
console.log("不支持 on 方法", channel, callback) |
|||
} |
|||
// 实现其他方法...
|
|||
on<K extends string>(channel: K, callback: (...args: any[]) => void): void { |
|||
// 浏览器中可能使用 WebSocket 或其他方式
|
|||
console.log("不支持 on 方法", channel, callback) |
|||
} |
|||
|
|||
off<K extends string>(channel: K, callback: (...args: any[]) => void): void { |
|||
// 相应的解绑实现
|
|||
console.log("不支持 on 方法", channel, callback) |
|||
} |
|||
off<K extends string>(channel: K, callback: (...args: any[]) => void): void { |
|||
// 相应的解绑实现
|
|||
console.log("不支持 on 方法", channel, callback) |
|||
} |
|||
|
|||
offAll<K extends string>(channel: K): void { |
|||
// 相应的全部解绑实现
|
|||
console.log("不支持 on 方法", channel) |
|||
} |
|||
offAll<K extends string>(channel: K): void { |
|||
// 相应的全部解绑实现
|
|||
console.log("不支持 on 方法", channel) |
|||
} |
|||
} |
|||
|
@ -1,20 +1,20 @@ |
|||
import { IApiClient } from "./abstract" |
|||
|
|||
export class ElectronApiClient implements IApiClient { |
|||
call<T = any>(command: string, ...args: any[]): Promise<T> { |
|||
// Electron 特定实现
|
|||
return window.api.call(command, ...args) |
|||
} |
|||
call<T = any>(command: string, ...args: any[]): Promise<T> { |
|||
// Electron 特定实现
|
|||
return window.api.call(command, ...args) |
|||
} |
|||
|
|||
on<K extends string>(channel: K, callback: (...args: any[]) => void): void { |
|||
window.api.on(channel, callback) |
|||
} |
|||
on<K extends string>(channel: K, callback: (...args: any[]) => void): void { |
|||
window.api.on(channel, callback) |
|||
} |
|||
|
|||
off<K extends string>(channel: K, callback: (...args: any[]) => void): void { |
|||
window.api.off(channel, callback) |
|||
} |
|||
off<K extends string>(channel: K, callback: (...args: any[]) => void): void { |
|||
window.api.off(channel, callback) |
|||
} |
|||
|
|||
offAll<K extends string>(channel: K): void { |
|||
window.api.offAll(channel) |
|||
} |
|||
offAll<K extends string>(channel: K): void { |
|||
window.api.offAll(channel) |
|||
} |
|||
} |
|||
|
@ -1,6 +1,6 @@ |
|||
export function Layout(width, height) { |
|||
// Tab布局位置
|
|||
const NavbarHeight = 30 |
|||
const OffsetHeight = NavbarHeight + 100 |
|||
return { x: 0, y: OffsetHeight, width: width, height: height - OffsetHeight } |
|||
// Tab布局位置
|
|||
const NavbarHeight = 30 |
|||
const OffsetHeight = NavbarHeight + 100 |
|||
return { x: 0, y: OffsetHeight, width: width, height: height - OffsetHeight } |
|||
} |
|||
|
@ -1,28 +1,28 @@ |
|||
<!doctype html> |
|||
<html lang="en"> |
|||
<head> |
|||
<meta charset="UTF-8" /> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
|||
<title>关于我</title> |
|||
<style> |
|||
html, |
|||
body { |
|||
height: 100%; |
|||
width: 100%; |
|||
margin: 0; |
|||
padding: 0; |
|||
outline: none; |
|||
border: 0; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
</style> |
|||
</head> |
|||
<head> |
|||
<meta charset="UTF-8" /> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
|||
<title>关于我</title> |
|||
<style> |
|||
html, |
|||
body { |
|||
height: 100%; |
|||
width: 100%; |
|||
margin: 0; |
|||
padding: 0; |
|||
outline: none; |
|||
border: 0; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
</style> |
|||
</head> |
|||
|
|||
<body> |
|||
<article> |
|||
<h1 id="demo" style="text-align: center">您好,亲爱的冒险者!</h1> |
|||
</article> |
|||
</body> |
|||
<body> |
|||
<article> |
|||
<h1 id="demo" style="text-align: center">您好,亲爱的冒险者!</h1> |
|||
</article> |
|||
</body> |
|||
</html> |
|||
|
File diff suppressed because it is too large
@ -1,26 +1,27 @@ |
|||
<!doctype html> |
|||
<html> |
|||
|
|||
<head> |
|||
<head> |
|||
<meta charset="UTF-8" /> |
|||
<title>Electron</title> |
|||
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP --> |
|||
<meta http-equiv="Content-Security-Policy" content="default-src 'self' api: 'unsafe-inline'; |
|||
<meta |
|||
http-equiv="Content-Security-Policy" |
|||
content="default-src 'self' api: 'unsafe-inline'; |
|||
script-src 'self' api:; |
|||
style-src 'self' 'unsafe-inline'; |
|||
img-src 'self' data: *;" /> |
|||
</head> |
|||
img-src 'self' data: *;" |
|||
/> |
|||
</head> |
|||
|
|||
<body> |
|||
<body> |
|||
<div id="app"></div> |
|||
<noscript> |
|||
<style> |
|||
[data-simplebar] { |
|||
overflow: auto; |
|||
} |
|||
</style> |
|||
<style> |
|||
[data-simplebar] { |
|||
overflow: auto; |
|||
} |
|||
</style> |
|||
</noscript> |
|||
<script type="module" src="/src/main.ts"></script> |
|||
</body> |
|||
|
|||
</body> |
|||
</html> |
|||
|
@ -1,30 +1,30 @@ |
|||
<script setup lang="ts"></script> |
|||
|
|||
<template> |
|||
<div h-full flex flex-col overflow-hidden> |
|||
<NavBar></NavBar> |
|||
<div flex-1 h-0 overflow-hidden flex flex-col> |
|||
<router-view v-slot="{ Component, route }"> |
|||
<Transition name="slide-fade" mode="out-in"> |
|||
<component :is="Component" :key="route.fullPath" /> |
|||
</Transition> |
|||
</router-view> |
|||
</div> |
|||
<div h-full flex flex-col overflow-hidden> |
|||
<NavBar></NavBar> |
|||
<div flex-1 h-0 overflow-hidden flex flex-col> |
|||
<router-view v-slot="{ Component, route }"> |
|||
<Transition name="slide-fade" mode="out-in"> |
|||
<component :is="Component" :key="route.fullPath" /> |
|||
</Transition> |
|||
</router-view> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<style lang="scss" scoped> |
|||
.slide-fade-enter-active { |
|||
.slide-fade-enter-active { |
|||
transition: all 0.2s ease-out; |
|||
} |
|||
} |
|||
|
|||
.slide-fade-leave-active { |
|||
.slide-fade-leave-active { |
|||
transition: all 0.2s cubic-bezier(1, 0.5, 0.8, 1); |
|||
} |
|||
} |
|||
|
|||
.slide-fade-enter-from, |
|||
.slide-fade-leave-to { |
|||
.slide-fade-enter-from, |
|||
.slide-fade-leave-to { |
|||
transform: translateX(20px); |
|||
opacity: 0; |
|||
} |
|||
} |
|||
</style> |
|||
|
@ -1,246 +1,245 @@ |
|||
interface ScrollStyle { |
|||
[key: string]: string |
|||
[key: string]: string |
|||
} |
|||
|
|||
class Scrollbot { |
|||
private orgPar!: HTMLElement |
|||
private sbw: number = 5 |
|||
private scrollSpeed: number = 200 |
|||
private parContent!: string |
|||
private newPar!: HTMLDivElement |
|||
// private sbContainer!: HTMLDivElement
|
|||
private scrollBarHolder!: HTMLDivElement |
|||
private scrollBar!: HTMLDivElement |
|||
private inP!: HTMLDivElement |
|||
private sbHeight: number = 0 |
|||
private mdown: boolean = false |
|||
private customHeight: boolean = false |
|||
// private scrollElement!: HTMLElement
|
|||
private onScrollF?: () => void |
|||
private sB: ScrollStyle = {} |
|||
private sBH: ScrollStyle = {} |
|||
private posCorrection: number = 0 |
|||
private btmCorrection: number = 0 |
|||
private relY: number = 0 |
|||
private pC: number = 0 |
|||
|
|||
getDom(selector: string | HTMLElement) { |
|||
if (typeof selector === "string") { |
|||
return document.querySelector<HTMLElement>(selector) |
|||
} |
|||
return selector |
|||
private orgPar!: HTMLElement |
|||
private sbw: number = 5 |
|||
private scrollSpeed: number = 200 |
|||
private parContent!: string |
|||
private newPar!: HTMLDivElement |
|||
// private sbContainer!: HTMLDivElement
|
|||
private scrollBarHolder!: HTMLDivElement |
|||
private scrollBar!: HTMLDivElement |
|||
private inP!: HTMLDivElement |
|||
private sbHeight: number = 0 |
|||
private mdown: boolean = false |
|||
private customHeight: boolean = false |
|||
// private scrollElement!: HTMLElement
|
|||
private onScrollF?: () => void |
|||
private sB: ScrollStyle = {} |
|||
private sBH: ScrollStyle = {} |
|||
private posCorrection: number = 0 |
|||
private btmCorrection: number = 0 |
|||
private relY: number = 0 |
|||
private pC: number = 0 |
|||
|
|||
getDom(selector: string | HTMLElement) { |
|||
if (typeof selector === "string") { |
|||
return document.querySelector<HTMLElement>(selector) |
|||
} |
|||
return selector |
|||
} |
|||
|
|||
constructor(selector: string | HTMLElement, width?: number) { |
|||
const element = this.getDom(selector) |
|||
if (!element) throw new Error("Element not found") |
|||
this.orgPar = element |
|||
constructor(selector: string | HTMLElement, width?: number) { |
|||
const element = this.getDom(selector) |
|||
if (!element) throw new Error("Element not found") |
|||
this.orgPar = element |
|||
|
|||
const ieVersion = this.isIE() |
|||
if (!ieVersion || (ieVersion && ieVersion < 9)) { |
|||
this.init(width) |
|||
} |
|||
const ieVersion = this.isIE() |
|||
if (!ieVersion || (ieVersion && ieVersion < 9)) { |
|||
this.init(width) |
|||
} |
|||
|
|||
private init(width?: number): void { |
|||
this.sbw = width ?? 5 |
|||
this.parContent = this.orgPar.innerHTML |
|||
this.orgPar.innerHTML = "" |
|||
|
|||
this.setupElements() |
|||
this.setupStyles() |
|||
this.setupEventListeners() |
|||
this.refresh() |
|||
} |
|||
|
|||
private init(width?: number): void { |
|||
this.sbw = width ?? 5 |
|||
this.parContent = this.orgPar.innerHTML |
|||
this.orgPar.innerHTML = "" |
|||
|
|||
this.setupElements() |
|||
this.setupStyles() |
|||
this.setupEventListeners() |
|||
this.refresh() |
|||
} |
|||
|
|||
private setupElements(): void { |
|||
this.newPar = document.createElement("div") |
|||
// this.sbContainer = document.createElement("div")
|
|||
this.scrollBarHolder = document.createElement("div") |
|||
this.scrollBar = document.createElement("div") |
|||
this.inP = document.createElement("div") |
|||
|
|||
this.newPar.className = "scrollbot-outer-parent" |
|||
this.scrollBarHolder.className = "scrollbot-scrollbar-holder" |
|||
this.scrollBar.className = "scrollbot-scrollbar" |
|||
this.inP.className = "scrollbot-inner-parent" |
|||
|
|||
this.inP.innerHTML = this.parContent |
|||
this.newPar.appendChild(this.inP) |
|||
this.scrollBarHolder.appendChild(this.scrollBar) |
|||
this.newPar.appendChild(this.scrollBarHolder) |
|||
this.orgPar.appendChild(this.newPar) |
|||
} |
|||
|
|||
private setupStyles(): void { |
|||
this.newPar.style.position = "relative" |
|||
this.newPar.style.paddingRight = `${this.sbw}px` |
|||
this.newPar.style.zIndex = "9999999" |
|||
this.newPar.style.height = "100%" |
|||
this.newPar.style.overflow = "hidden" |
|||
|
|||
this.inP.style.cssText = `height:100%;overflow-y:auto;overflow-x:hidden;padding-right:${ |
|||
this.sbw + 20 |
|||
}px;width:100%;box-sizing:content-box;` |
|||
|
|||
this.sbHeight = (this.inP.clientHeight * 100) / this.inP.scrollHeight |
|||
// this.scrollElement = this.inP
|
|||
|
|||
this.updateScrollbarStyles() |
|||
} |
|||
|
|||
private updateScrollbarStyles(): void { |
|||
this.sB = { |
|||
width: `${this.sbw}px`, |
|||
height: `${this.sbHeight}%`, |
|||
position: "absolute", |
|||
right: "0", |
|||
top: "0", |
|||
backgroundColor: "#444444", |
|||
borderRadius: "15px", |
|||
} |
|||
|
|||
private setupElements(): void { |
|||
this.newPar = document.createElement("div") |
|||
// this.sbContainer = document.createElement("div")
|
|||
this.scrollBarHolder = document.createElement("div") |
|||
this.scrollBar = document.createElement("div") |
|||
this.inP = document.createElement("div") |
|||
|
|||
this.newPar.className = "scrollbot-outer-parent" |
|||
this.scrollBarHolder.className = "scrollbot-scrollbar-holder" |
|||
this.scrollBar.className = "scrollbot-scrollbar" |
|||
this.inP.className = "scrollbot-inner-parent" |
|||
|
|||
this.inP.innerHTML = this.parContent |
|||
this.newPar.appendChild(this.inP) |
|||
this.scrollBarHolder.appendChild(this.scrollBar) |
|||
this.newPar.appendChild(this.scrollBarHolder) |
|||
this.orgPar.appendChild(this.newPar) |
|||
} |
|||
|
|||
private setupStyles(): void { |
|||
this.newPar.style.position = "relative" |
|||
this.newPar.style.paddingRight = `${this.sbw}px` |
|||
this.newPar.style.zIndex = "9999999" |
|||
this.newPar.style.height = "100%" |
|||
this.newPar.style.overflow = "hidden" |
|||
|
|||
this.inP.style.cssText = `height:100%;overflow-y:auto;overflow-x:hidden;padding-right:${ |
|||
this.sbw + 20 |
|||
}px;width:100%;box-sizing:content-box;` |
|||
|
|||
this.sbHeight = (this.inP.clientHeight * 100) / this.inP.scrollHeight |
|||
// this.scrollElement = this.inP
|
|||
|
|||
this.updateScrollbarStyles() |
|||
this.sBH = { |
|||
width: `${this.sbw}px`, |
|||
height: "100%", |
|||
position: "absolute", |
|||
right: "0", |
|||
top: "0", |
|||
backgroundColor: "#ADADAD", |
|||
borderRadius: "15px", |
|||
} |
|||
|
|||
private updateScrollbarStyles(): void { |
|||
this.sB = { |
|||
width: `${this.sbw}px`, |
|||
height: `${this.sbHeight}%`, |
|||
position: "absolute", |
|||
right: "0", |
|||
top: "0", |
|||
backgroundColor: "#444444", |
|||
borderRadius: "15px", |
|||
} |
|||
Object.assign(this.scrollBar.style, this.sB) |
|||
Object.assign(this.scrollBarHolder.style, this.sBH) |
|||
} |
|||
|
|||
this.sBH = { |
|||
width: `${this.sbw}px`, |
|||
height: "100%", |
|||
position: "absolute", |
|||
right: "0", |
|||
top: "0", |
|||
backgroundColor: "#ADADAD", |
|||
borderRadius: "15px", |
|||
} |
|||
public refresh(): void { |
|||
this.sbHeight = (this.inP.clientHeight * 100) / this.inP.scrollHeight |
|||
this.scrollBarHolder.style.display = this.sbHeight >= 100 ? "none" : "block" |
|||
|
|||
Object.assign(this.scrollBar.style, this.sB) |
|||
Object.assign(this.scrollBarHolder.style, this.sBH) |
|||
if (this.inP.scrollHeight > this.inP.clientHeight) { |
|||
this.scrollBar.style.height = this.customHeight ? this.sB.height : `${this.sbHeight}%` |
|||
} |
|||
|
|||
public refresh(): void { |
|||
this.sbHeight = (this.inP.clientHeight * 100) / this.inP.scrollHeight |
|||
this.scrollBarHolder.style.display = this.sbHeight >= 100 ? "none" : "block" |
|||
|
|||
if (this.inP.scrollHeight > this.inP.clientHeight) { |
|||
this.scrollBar.style.height = this.customHeight ? this.sB.height : `${this.sbHeight}%` |
|||
} |
|||
} |
|||
|
|||
public destroy(): void { |
|||
this.orgPar.innerHTML = this.parContent |
|||
this.orgPar.style.overflow = "auto" |
|||
} |
|||
|
|||
private isIE(): number | false { |
|||
const userAgent = navigator.userAgent.toLowerCase() |
|||
const msie = userAgent.indexOf("msie") |
|||
return msie !== -1 ? parseInt(userAgent.split("msie")[1]) : false |
|||
} |
|||
|
|||
public onScroll(callback: () => void): void { |
|||
this.onScrollF = callback |
|||
} |
|||
|
|||
private setupEventListeners(): void { |
|||
this.setupScrollListener() |
|||
this.setupMouseEvents() |
|||
} |
|||
|
|||
private setupScrollListener(): void { |
|||
this.inP.addEventListener("scroll", () => { |
|||
const scrollPercentage = (this.inP.scrollTop * 100) / this.inP.scrollHeight |
|||
const correction = |
|||
((this.sbHeight - parseFloat(this.sB.height)) * this.inP.scrollTop) / (this.inP.scrollHeight - this.inP.clientHeight) |
|||
|
|||
this.scrollBar.style.top = `${scrollPercentage + correction}%` |
|||
|
|||
if (this.onScrollF) { |
|||
this.onScrollF() |
|||
} |
|||
}) |
|||
} |
|||
|
|||
private setScroll(position: number, duration: number = 500): void { |
|||
if (position >= this.inP.scrollHeight - this.inP.clientHeight) { |
|||
position = this.inP.scrollHeight - this.inP.clientHeight |
|||
} |
|||
|
|||
public destroy(): void { |
|||
this.orgPar.innerHTML = this.parContent |
|||
this.orgPar.style.overflow = "auto" |
|||
const difference = position - this.inP.scrollTop |
|||
const perTick = (difference / duration) * 10 |
|||
|
|||
setTimeout(() => { |
|||
this.inP.scrollTop += perTick |
|||
if (Math.abs(position - this.inP.scrollTop) < 5) return |
|||
this.setScroll(position, duration - 10) |
|||
}, 10) |
|||
} |
|||
|
|||
private setupMouseEvents(): void { |
|||
// 滚动条容器点击事件
|
|||
this.scrollBarHolder.onmousedown = (e: MouseEvent) => { |
|||
if (e.target !== this.scrollBarHolder) return |
|||
const relPos = ((e.pageY - this.scrollBarHolder.getBoundingClientRect().top) * 100) / this.scrollBarHolder.clientHeight |
|||
this.setScroll((this.inP.scrollHeight * relPos) / 100, this.scrollSpeed) |
|||
} |
|||
|
|||
private isIE(): number | false { |
|||
const userAgent = navigator.userAgent.toLowerCase() |
|||
const msie = userAgent.indexOf("msie") |
|||
return msie !== -1 ? parseInt(userAgent.split("msie")[1]) : false |
|||
// 滚动条拖动事件
|
|||
this.scrollBar.onmousedown = (e: MouseEvent) => { |
|||
this.mdown = true |
|||
this.posCorrection = e.pageY - this.scrollBar.getBoundingClientRect().top |
|||
this.btmCorrection = (this.scrollBar.clientHeight * 100) / this.newPar.clientHeight |
|||
return false |
|||
} |
|||
|
|||
public onScroll(callback: () => void): void { |
|||
this.onScrollF = callback |
|||
// 全局鼠标事件
|
|||
document.onmouseup = () => { |
|||
this.mdown = false |
|||
} |
|||
|
|||
private setupEventListeners(): void { |
|||
this.setupScrollListener() |
|||
this.setupMouseEvents() |
|||
} |
|||
|
|||
private setupScrollListener(): void { |
|||
this.inP.addEventListener("scroll", () => { |
|||
const scrollPercentage = (this.inP.scrollTop * 100) / this.inP.scrollHeight |
|||
const correction = |
|||
((this.sbHeight - parseFloat(this.sB.height)) * this.inP.scrollTop) / (this.inP.scrollHeight - this.inP.clientHeight) |
|||
|
|||
this.scrollBar.style.top = `${scrollPercentage + correction}%` |
|||
|
|||
if (this.onScrollF) { |
|||
this.onScrollF() |
|||
} |
|||
}) |
|||
} |
|||
|
|||
private setScroll(position: number, duration: number = 500): void { |
|||
if (position >= this.inP.scrollHeight - this.inP.clientHeight) { |
|||
position = this.inP.scrollHeight - this.inP.clientHeight |
|||
} |
|||
|
|||
const difference = position - this.inP.scrollTop |
|||
const perTick = (difference / duration) * 10 |
|||
|
|||
setTimeout(() => { |
|||
this.inP.scrollTop += perTick |
|||
if (Math.abs(position - this.inP.scrollTop) < 5) return |
|||
this.setScroll(position, duration - 10) |
|||
}, 10) |
|||
} |
|||
|
|||
private setupMouseEvents(): void { |
|||
// 滚动条容器点击事件
|
|||
this.scrollBarHolder.onmousedown = (e: MouseEvent) => { |
|||
if (e.target !== this.scrollBarHolder) return |
|||
const relPos = ((e.pageY - this.scrollBarHolder.getBoundingClientRect().top) * 100) / this.scrollBarHolder.clientHeight |
|||
this.setScroll((this.inP.scrollHeight * relPos) / 100, this.scrollSpeed) |
|||
} |
|||
|
|||
// 滚动条拖动事件
|
|||
this.scrollBar.onmousedown = (e: MouseEvent) => { |
|||
this.mdown = true |
|||
this.posCorrection = e.pageY - this.scrollBar.getBoundingClientRect().top |
|||
this.btmCorrection = (this.scrollBar.clientHeight * 100) / this.newPar.clientHeight |
|||
return false |
|||
document.onmousemove = (e: MouseEvent) => { |
|||
if (this.mdown) { |
|||
// 清除文本选择
|
|||
window.getSelection()?.removeAllRanges() |
|||
|
|||
this.relY = e.pageY - this.newPar.getBoundingClientRect().top |
|||
this.pC = ((this.relY - this.posCorrection) * 100) / this.newPar.clientHeight |
|||
|
|||
if (this.pC >= 0 && this.pC + this.btmCorrection <= 100) { |
|||
this.scrollBar.style.top = `${this.pC}%` |
|||
this.inP.scrollTop = |
|||
((parseFloat(this.scrollBar.style.top) - |
|||
((this.sbHeight - parseFloat(this.sB.height)) * this.inP.scrollTop) / (this.inP.scrollHeight - this.inP.clientHeight)) * |
|||
this.inP.scrollHeight) / |
|||
100 |
|||
} else if (this.pC < 0 && parseFloat(this.scrollBar.style.top) > 0) { |
|||
this.scrollBar.style.top = "0%" |
|||
this.inP.scrollTop = 0 |
|||
} |
|||
|
|||
// 全局鼠标事件
|
|||
document.onmouseup = () => { |
|||
this.mdown = false |
|||
} |
|||
|
|||
document.onmousemove = (e: MouseEvent) => { |
|||
if (this.mdown) { |
|||
// 清除文本选择
|
|||
window.getSelection()?.removeAllRanges() |
|||
|
|||
this.relY = e.pageY - this.newPar.getBoundingClientRect().top |
|||
this.pC = ((this.relY - this.posCorrection) * 100) / this.newPar.clientHeight |
|||
|
|||
if (this.pC >= 0 && this.pC + this.btmCorrection <= 100) { |
|||
this.scrollBar.style.top = `${this.pC}%` |
|||
this.inP.scrollTop = |
|||
((parseFloat(this.scrollBar.style.top) - |
|||
((this.sbHeight - parseFloat(this.sB.height)) * this.inP.scrollTop) / |
|||
(this.inP.scrollHeight - this.inP.clientHeight)) * |
|||
this.inP.scrollHeight) / |
|||
100 |
|||
} else if (this.pC < 0 && parseFloat(this.scrollBar.style.top) > 0) { |
|||
this.scrollBar.style.top = "0%" |
|||
this.inP.scrollTop = 0 |
|||
} |
|||
|
|||
if (this.onScrollF) { |
|||
this.onScrollF() |
|||
} |
|||
} |
|||
return false |
|||
if (this.onScrollF) { |
|||
this.onScrollF() |
|||
} |
|||
} |
|||
return false |
|||
} |
|||
} |
|||
|
|||
public setStyle(scrollbar?: ScrollStyle, scrollbarHolder?: ScrollStyle): Scrollbot { |
|||
if (scrollbar) { |
|||
scrollbar.width = `${this.sbw}px` |
|||
if ("height" in scrollbar) { |
|||
this.customHeight = true |
|||
scrollbar.height = `${(parseFloat(scrollbar.height) * 100) / this.newPar.clientHeight}%` |
|||
} |
|||
Object.assign(this.sB, scrollbar) |
|||
Object.assign(this.scrollBar.style, scrollbar) |
|||
} |
|||
|
|||
public setStyle(scrollbar?: ScrollStyle, scrollbarHolder?: ScrollStyle): Scrollbot { |
|||
if (scrollbar) { |
|||
scrollbar.width = `${this.sbw}px` |
|||
if ("height" in scrollbar) { |
|||
this.customHeight = true |
|||
scrollbar.height = `${(parseFloat(scrollbar.height) * 100) / this.newPar.clientHeight}%` |
|||
} |
|||
Object.assign(this.sB, scrollbar) |
|||
Object.assign(this.scrollBar.style, scrollbar) |
|||
} |
|||
|
|||
if (scrollbarHolder) { |
|||
scrollbarHolder.width = `${this.sbw}px` |
|||
Object.assign(this.sBH, scrollbarHolder) |
|||
Object.assign(this.scrollBarHolder.style, scrollbarHolder) |
|||
} |
|||
|
|||
return this |
|||
if (scrollbarHolder) { |
|||
scrollbarHolder.width = `${this.sbw}px` |
|||
Object.assign(this.sBH, scrollbarHolder) |
|||
Object.assign(this.scrollBarHolder.style, scrollbarHolder) |
|||
} |
|||
|
|||
return this |
|||
} |
|||
} |
|||
|
|||
export default Scrollbot |
|||
|
@ -1,44 +1,44 @@ |
|||
*, |
|||
*::before, |
|||
*::after { |
|||
box-sizing: border-box; |
|||
margin: 0; |
|||
font-weight: normal; |
|||
box-sizing: border-box; |
|||
margin: 0; |
|||
font-weight: normal; |
|||
} |
|||
|
|||
html { |
|||
--text-normal: #6b6b6b; |
|||
--text-hover: #000000; |
|||
height: 100%; |
|||
--text-normal: #6b6b6b; |
|||
--text-hover: #000000; |
|||
height: 100%; |
|||
} |
|||
|
|||
body { |
|||
--at-apply: text-normal; |
|||
height: 100%; |
|||
--at-apply: text-normal; |
|||
height: 100%; |
|||
} |
|||
|
|||
#app { |
|||
height: 100%; |
|||
height: 100%; |
|||
} |
|||
|
|||
* { |
|||
user-select: none; |
|||
outline: none; |
|||
user-select: none; |
|||
outline: none; |
|||
} |
|||
|
|||
.simplebar-scrollbar::before { |
|||
background-color: #bdbdbd; |
|||
border-radius: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
top: 0; |
|||
background-color: #bdbdbd; |
|||
border-radius: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
top: 0; |
|||
} |
|||
|
|||
.simplebar-hover .simplebar-scrollbar::before { |
|||
background-color: #909090; |
|||
background-color: #909090; |
|||
} |
|||
|
|||
.simplebar-wrapper:hover ~ .simplebar-track > .simplebar-scrollbar:before { |
|||
opacity: 0.5 !important; |
|||
opacity: 0.5 !important; |
|||
} |
|||
|
@ -1 +1 @@ |
|||
type A = string |
|||
type A = string |
|||
|
@ -1,300 +1,299 @@ |
|||
<script lang="ts" setup> |
|||
import { judgeFile } from "./utils" |
|||
import { monaco } from "./monaco" |
|||
import { computed, getCurrentScope, onBeforeUnmount, onMounted, onScopeDispose, ref, watch } from "vue" |
|||
import DefaultLogo from "./120x120.png" |
|||
import { PlaceholderContentWidget } from "./PlaceholderContentWidget" |
|||
const editorRef = ref<HTMLDivElement>() |
|||
let editor: monaco.editor.IStandaloneCodeEditor | null = null |
|||
let placeholderWidget: PlaceholderContentWidget | null = null |
|||
const props = withDefaults( |
|||
import { judgeFile } from "./utils" |
|||
import { monaco } from "./monaco" |
|||
import { computed, getCurrentScope, onBeforeUnmount, onMounted, onScopeDispose, ref, watch } from "vue" |
|||
import DefaultLogo from "./120x120.png" |
|||
import { PlaceholderContentWidget } from "./PlaceholderContentWidget" |
|||
const editorRef = ref<HTMLDivElement>() |
|||
let editor: monaco.editor.IStandaloneCodeEditor | null = null |
|||
let placeholderWidget: PlaceholderContentWidget | null = null |
|||
const props = withDefaults( |
|||
defineProps<{ |
|||
modelValue?: string |
|||
name?: string |
|||
logoType?: "bg" | "logo" |
|||
logo?: string |
|||
placeholder?: string |
|||
fontFamily?: string |
|||
readonly?: boolean |
|||
modelValue?: string |
|||
name?: string |
|||
logoType?: "bg" | "logo" |
|||
logo?: string |
|||
placeholder?: string |
|||
fontFamily?: string |
|||
readonly?: boolean |
|||
}>(), |
|||
{ |
|||
logo: DefaultLogo, |
|||
readonly: false, |
|||
logoType: "logo", |
|||
modelValue: "", |
|||
name: "", |
|||
logo: DefaultLogo, |
|||
readonly: false, |
|||
logoType: "logo", |
|||
modelValue: "", |
|||
name: "", |
|||
}, |
|||
) |
|||
const emit = defineEmits<{ |
|||
) |
|||
const emit = defineEmits<{ |
|||
(e: "update:modelValue", code: string): void |
|||
(e: "change", code: string): void |
|||
(e: "cursor:position", position: [number, number]): void |
|||
}>() |
|||
defineExpose({ |
|||
}>() |
|||
defineExpose({ |
|||
scrollTop() { |
|||
editor?.setScrollTop(0) |
|||
editor?.setScrollTop(0) |
|||
}, |
|||
insertText(text: string, type = "cursor") { |
|||
if (editor) { |
|||
const m = editor.getModel() |
|||
const currentPosition = editor.getPosition() |
|||
if (m) { |
|||
console.log(currentPosition) |
|||
if (type === "cursor" && currentPosition) { |
|||
m.pushEditOperations( |
|||
[], |
|||
[ |
|||
{ |
|||
range: new monaco.Range( |
|||
currentPosition.lineNumber, |
|||
currentPosition.column, |
|||
currentPosition.lineNumber, |
|||
currentPosition.column, |
|||
), |
|||
text, |
|||
}, |
|||
], |
|||
() => [ |
|||
new monaco.Selection( |
|||
currentPosition.lineNumber, |
|||
currentPosition.column, |
|||
currentPosition.lineNumber, |
|||
currentPosition.column, |
|||
), |
|||
], |
|||
) |
|||
} else { |
|||
const lineCount = m.getLineCount() |
|||
const lastLineLength = m.getLineLength(lineCount) |
|||
const range = new monaco.Selection(lineCount, lastLineLength + 1, lineCount, lastLineLength + 1) |
|||
const text = "your text" |
|||
const op = { |
|||
range: range, |
|||
text: text, |
|||
} |
|||
m.pushEditOperations([], [op], () => [range]) |
|||
} |
|||
if (editor) { |
|||
const m = editor.getModel() |
|||
const currentPosition = editor.getPosition() |
|||
if (m) { |
|||
console.log(currentPosition) |
|||
if (type === "cursor" && currentPosition) { |
|||
m.pushEditOperations( |
|||
[], |
|||
[ |
|||
{ |
|||
range: new monaco.Range( |
|||
currentPosition.lineNumber, |
|||
currentPosition.column, |
|||
currentPosition.lineNumber, |
|||
currentPosition.column, |
|||
), |
|||
text, |
|||
}, |
|||
], |
|||
() => [ |
|||
new monaco.Selection( |
|||
currentPosition.lineNumber, |
|||
currentPosition.column, |
|||
currentPosition.lineNumber, |
|||
currentPosition.column, |
|||
), |
|||
], |
|||
) |
|||
} else { |
|||
const lineCount = m.getLineCount() |
|||
const lastLineLength = m.getLineLength(lineCount) |
|||
const range = new monaco.Selection(lineCount, lastLineLength + 1, lineCount, lastLineLength + 1) |
|||
const text = "your text" |
|||
const op = { |
|||
range: range, |
|||
text: text, |
|||
} |
|||
m.pushEditOperations([], [op], () => [range]) |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
setContent(content: string) { |
|||
if (editorRef.value && editor) { |
|||
editor.setValue(content) |
|||
} |
|||
if (editorRef.value && editor) { |
|||
editor.setValue(content) |
|||
} |
|||
}, |
|||
}) |
|||
}) |
|||
|
|||
let isInnerChange = false |
|||
function updateModel(name: string, content: string) { |
|||
let isInnerChange = false |
|||
function updateModel(name: string, content: string) { |
|||
if (editor) { |
|||
const oldModel = editor.getModel() //获取旧模型 |
|||
const file = judgeFile(name) |
|||
// 这样定义的话model无法清除 |
|||
// monaco.editor.createModel("const a = 111","typescript", monaco.Uri.parse('file://root/file3.ts')) |
|||
const model: monaco.editor.ITextModel = monaco.editor.createModel(content ?? "", file?.language ?? "txt") |
|||
model.onDidChangeContent(() => { |
|||
if (model) { |
|||
isInnerChange = true |
|||
const code = model.getValue() |
|||
emit("update:modelValue", code) |
|||
emit("change", code) |
|||
} |
|||
}) |
|||
if (oldModel) { |
|||
oldModel.dispose() |
|||
const oldModel = editor.getModel() //获取旧模型 |
|||
const file = judgeFile(name) |
|||
// 这样定义的话model无法清除 |
|||
// monaco.editor.createModel("const a = 111","typescript", monaco.Uri.parse('file://root/file3.ts')) |
|||
const model: monaco.editor.ITextModel = monaco.editor.createModel(content ?? "", file?.language ?? "txt") |
|||
model.onDidChangeContent(() => { |
|||
if (model) { |
|||
isInnerChange = true |
|||
const code = model.getValue() |
|||
emit("update:modelValue", code) |
|||
emit("change", code) |
|||
} |
|||
editor.setModel(model) |
|||
}) |
|||
if (oldModel) { |
|||
oldModel.dispose() |
|||
} |
|||
editor.setModel(model) |
|||
} |
|||
} |
|||
function resizeLayout() { |
|||
} |
|||
function resizeLayout() { |
|||
if (editor) { |
|||
editor.layout() |
|||
editor.layout() |
|||
} |
|||
} |
|||
} |
|||
|
|||
onMounted(() => { |
|||
onMounted(() => { |
|||
if (editorRef.value && !editor) { |
|||
editor = monaco.editor.create(editorRef.value, { |
|||
theme: "vs-light", |
|||
fontFamily: props.fontFamily ?? "Cascadia Mono, Consolas, 'Courier New', monospace", |
|||
readOnly: props.readonly, |
|||
minimap: { |
|||
autohide: true, |
|||
}, |
|||
}) as monaco.editor.IStandaloneCodeEditor |
|||
editor.onDidChangeCursorPosition(e => { |
|||
emit("cursor:position", [e.position.lineNumber, e.position.column]) |
|||
}) |
|||
editorRef.value.addEventListener("resize", resizeLayout) |
|||
editor = monaco.editor.create(editorRef.value, { |
|||
theme: "vs-light", |
|||
fontFamily: props.fontFamily ?? "Cascadia Mono, Consolas, 'Courier New', monospace", |
|||
readOnly: props.readonly, |
|||
minimap: { |
|||
autohide: true, |
|||
}, |
|||
}) as monaco.editor.IStandaloneCodeEditor |
|||
editor.onDidChangeCursorPosition(e => { |
|||
emit("cursor:position", [e.position.lineNumber, e.position.column]) |
|||
}) |
|||
editorRef.value.addEventListener("resize", resizeLayout) |
|||
} |
|||
watch( |
|||
() => props.placeholder, |
|||
() => { |
|||
if (editor) { |
|||
if (placeholderWidget) { |
|||
placeholderWidget.dispose() |
|||
placeholderWidget = null |
|||
} |
|||
if (props.placeholder) { |
|||
placeholderWidget = new PlaceholderContentWidget(props.placeholder, editor) |
|||
} |
|||
} |
|||
}, |
|||
{ |
|||
immediate: true, |
|||
}, |
|||
() => props.placeholder, |
|||
() => { |
|||
if (editor) { |
|||
if (placeholderWidget) { |
|||
placeholderWidget.dispose() |
|||
placeholderWidget = null |
|||
} |
|||
if (props.placeholder) { |
|||
placeholderWidget = new PlaceholderContentWidget(props.placeholder, editor) |
|||
} |
|||
} |
|||
}, |
|||
{ |
|||
immediate: true, |
|||
}, |
|||
) |
|||
// 如果不需要从动态外部更改代码的话应该就不需要这个 |
|||
watch( |
|||
() => props.modelValue, |
|||
async str => { |
|||
if (editor && !isInnerChange) { |
|||
editor.setValue(str) |
|||
} else { |
|||
isInnerChange = false |
|||
} |
|||
}, |
|||
{ immediate: true }, |
|||
() => props.modelValue, |
|||
async str => { |
|||
if (editor && !isInnerChange) { |
|||
editor.setValue(str) |
|||
} else { |
|||
isInnerChange = false |
|||
} |
|||
}, |
|||
{ immediate: true }, |
|||
) |
|||
watch( |
|||
() => props.name, |
|||
async name => { |
|||
if (editor) { |
|||
updateModel(name, props.modelValue) |
|||
} |
|||
}, |
|||
{ immediate: true }, |
|||
() => props.name, |
|||
async name => { |
|||
if (editor) { |
|||
updateModel(name, props.modelValue) |
|||
} |
|||
}, |
|||
{ immediate: true }, |
|||
) |
|||
watch( |
|||
() => props.readonly, |
|||
() => { |
|||
if (editor) { |
|||
editor.updateOptions({ |
|||
readOnly: props.readonly, |
|||
}) |
|||
} |
|||
}, |
|||
() => props.readonly, |
|||
() => { |
|||
if (editor) { |
|||
editor.updateOptions({ |
|||
readOnly: props.readonly, |
|||
}) |
|||
} |
|||
}, |
|||
) |
|||
watch( |
|||
() => props.fontFamily, |
|||
() => { |
|||
if (editor) { |
|||
editor.updateOptions({ |
|||
fontFamily: props.fontFamily, |
|||
}) |
|||
} |
|||
}, |
|||
() => props.fontFamily, |
|||
() => { |
|||
if (editor) { |
|||
editor.updateOptions({ |
|||
fontFamily: props.fontFamily, |
|||
}) |
|||
} |
|||
}, |
|||
) |
|||
}) |
|||
if (import.meta.hot) { |
|||
import.meta.hot.accept((newModule) => { |
|||
console.log(newModule); |
|||
|
|||
}) |
|||
if (import.meta.hot) { |
|||
import.meta.hot.accept(newModule => { |
|||
console.log(newModule) |
|||
}) |
|||
} |
|||
onBeforeUnmount(() => { |
|||
} |
|||
onBeforeUnmount(() => { |
|||
if (editorRef.value) { |
|||
editorRef.value.removeEventListener("resize", resizeLayout) |
|||
editorRef.value.removeEventListener("resize", resizeLayout) |
|||
} |
|||
if (editor) { |
|||
const oldModel = editor.getModel() |
|||
if (oldModel) { |
|||
oldModel.dispose() |
|||
} |
|||
editor.dispose() |
|||
editor = null |
|||
const oldModel = editor.getModel() |
|||
if (oldModel) { |
|||
oldModel.dispose() |
|||
} |
|||
editor.dispose() |
|||
editor = null |
|||
} |
|||
}) |
|||
const style = computed(() => { |
|||
}) |
|||
const style = computed(() => { |
|||
if (props.logo && props.logoType === "bg") { |
|||
return { |
|||
backgroundImage: `url(${props.logo})`, |
|||
backgroundSize: "cover", |
|||
backgroundRepeat: "no-repeat", |
|||
backgroundPosition: "center center", |
|||
} |
|||
return { |
|||
backgroundImage: `url(${props.logo})`, |
|||
backgroundSize: "cover", |
|||
backgroundRepeat: "no-repeat", |
|||
backgroundPosition: "center center", |
|||
} |
|||
} |
|||
return {} |
|||
}) |
|||
}) |
|||
|
|||
const getLogo = computed(() => { |
|||
const getLogo = computed(() => { |
|||
if (props.logo) return props.logo |
|||
return DefaultLogo |
|||
}) |
|||
}) |
|||
|
|||
function useResizeObserver(callback: ResizeObserverCallback) { |
|||
function useResizeObserver(callback: ResizeObserverCallback) { |
|||
const isSupported = window && "ResizeObserver" in window |
|||
let observer: ResizeObserver | undefined |
|||
const cleanup = () => { |
|||
if (observer) { |
|||
observer.disconnect() |
|||
observer = undefined |
|||
} |
|||
if (observer) { |
|||
observer.disconnect() |
|||
observer = undefined |
|||
} |
|||
} |
|||
const stopWatch = watch( |
|||
() => editorRef.value, |
|||
el => { |
|||
cleanup() |
|||
if (isSupported && window && el) { |
|||
observer = new ResizeObserver(callback) |
|||
observer!.observe(el, {}) |
|||
} |
|||
}, |
|||
{ immediate: true }, |
|||
() => editorRef.value, |
|||
el => { |
|||
cleanup() |
|||
if (isSupported && window && el) { |
|||
observer = new ResizeObserver(callback) |
|||
observer!.observe(el, {}) |
|||
} |
|||
}, |
|||
{ immediate: true }, |
|||
) |
|||
const stop = () => { |
|||
cleanup() |
|||
stopWatch() |
|||
cleanup() |
|||
stopWatch() |
|||
} |
|||
function tryOnScopeDispose(fn: () => void) { |
|||
if (getCurrentScope()) { |
|||
onScopeDispose(fn) |
|||
return true |
|||
} |
|||
return false |
|||
if (getCurrentScope()) { |
|||
onScopeDispose(fn) |
|||
return true |
|||
} |
|||
return false |
|||
} |
|||
tryOnScopeDispose(() => { |
|||
stop() |
|||
stop() |
|||
}) |
|||
} |
|||
useResizeObserver(() => { |
|||
} |
|||
useResizeObserver(() => { |
|||
if (editor) { |
|||
editor.layout() |
|||
editor.layout() |
|||
} |
|||
}) |
|||
}) |
|||
</script> |
|||
|
|||
<template> |
|||
<div class="monaco-wrapper"> |
|||
<div class="monaco-editor" ref="editorRef"></div> |
|||
<div class="monaco-bg" :style="style"> |
|||
<img v-if="logoType === 'logo' && getLogo" class="monaco-logo" :src="getLogo" alt="" /> |
|||
</div> |
|||
<div class="monaco-wrapper"> |
|||
<div class="monaco-editor" ref="editorRef"></div> |
|||
<div class="monaco-bg" :style="style"> |
|||
<img v-if="logoType === 'logo' && getLogo" class="monaco-logo" :src="getLogo" alt="" /> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<style lang="scss" scoped> |
|||
.monaco-wrapper { |
|||
.monaco-wrapper { |
|||
height: 100%; |
|||
position: relative; |
|||
|
|||
.monaco-editor { |
|||
height: 100%; |
|||
height: 100%; |
|||
} |
|||
|
|||
.monaco-bg { |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
pointer-events: none; |
|||
opacity: 0.1; |
|||
overflow: hidden; |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
pointer-events: none; |
|||
opacity: 0.1; |
|||
overflow: hidden; |
|||
|
|||
.monaco-logo { |
|||
@apply absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2; |
|||
} |
|||
.monaco-logo { |
|||
@apply absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</style> |
|||
|
@ -1,3 +1,3 @@ |
|||
占位符 |
|||
https://github.com/Microsoft/monaco-editor/issues/1228 |
|||
https://github.com/microsoft/monaco-editor/issues/568#issuecomment-1499966160 |
|||
https://github.com/microsoft/monaco-editor/issues/568#issuecomment-1499966160 |
|||
|
@ -1,32 +1,32 @@ |
|||
export function judgeFile(filename: string) { |
|||
if (!filename) return |
|||
let ext = [ |
|||
{ language: "vue", ext: ".vue", index: -1 }, |
|||
{ language: "javascript", ext: ".js", index: -1 }, |
|||
{ language: "css", ext: ".css", index: -1 }, |
|||
{ language: "html", ext: ".html", index: -1 }, |
|||
{ language: "tsx", ext: ".tsx", index: -1 }, |
|||
{ language: "typescript", ext: ".ts", index: -1 }, |
|||
{ language: "markdown", ext: ".md", index: -1 }, |
|||
{ language: "json", ext: ".json", index: -1 }, |
|||
{ language: "web", ext: ".web", index: -1 }, |
|||
{ language: "dot", pre: ".", index: -1 }, |
|||
] |
|||
let cur |
|||
for (let i = 0; i < ext.length; i++) { |
|||
const e = ext[i] |
|||
if (e.ext && filename.endsWith(e.ext)) { |
|||
let index = filename.lastIndexOf(e.ext) |
|||
e.index = index |
|||
cur = e |
|||
break |
|||
} |
|||
if (e.pre && filename.startsWith(e.pre)) { |
|||
let index = filename.indexOf(e.pre) |
|||
e.index = index |
|||
cur = e |
|||
break |
|||
} |
|||
if (!filename) return |
|||
let ext = [ |
|||
{ language: "vue", ext: ".vue", index: -1 }, |
|||
{ language: "javascript", ext: ".js", index: -1 }, |
|||
{ language: "css", ext: ".css", index: -1 }, |
|||
{ language: "html", ext: ".html", index: -1 }, |
|||
{ language: "tsx", ext: ".tsx", index: -1 }, |
|||
{ language: "typescript", ext: ".ts", index: -1 }, |
|||
{ language: "markdown", ext: ".md", index: -1 }, |
|||
{ language: "json", ext: ".json", index: -1 }, |
|||
{ language: "web", ext: ".web", index: -1 }, |
|||
{ language: "dot", pre: ".", index: -1 }, |
|||
] |
|||
let cur |
|||
for (let i = 0; i < ext.length; i++) { |
|||
const e = ext[i] |
|||
if (e.ext && filename.endsWith(e.ext)) { |
|||
let index = filename.lastIndexOf(e.ext) |
|||
e.index = index |
|||
cur = e |
|||
break |
|||
} |
|||
return cur |
|||
if (e.pre && filename.startsWith(e.pre)) { |
|||
let index = filename.indexOf(e.pre) |
|||
e.index = index |
|||
cur = e |
|||
break |
|||
} |
|||
} |
|||
return cur |
|||
} |
|||
|
@ -1,102 +1,102 @@ |
|||
<template> |
|||
<div |
|||
relative |
|||
h="30px" |
|||
leading="29px" |
|||
pr="137px" |
|||
:style="{ paddingRight: isFullScreen ? '0' : '' }" |
|||
select-none |
|||
border-b="1px solid #E5E5E5" |
|||
bg="#F8F8F8" |
|||
> |
|||
<div absolute top-0 right-0 bottom-0 left-0 style="-webkit-app-region: drag"></div> |
|||
<div h-full px-2 flex items-center gap-1 justify-between> |
|||
<div flex items-center gap-1> |
|||
<img w="16px" h="16px" :src="icon" /> |
|||
<div relative h-full inline-flex items-center text-sm>{{ config.app_title }}</div> |
|||
<div relative class="list"> |
|||
<div class="item" @click="onClickMenu">{{ t("caidan") }}</div> |
|||
</div> |
|||
</div> |
|||
<div float-right h-full flex items-center relative style="-webkit-app-region: no-drag"> |
|||
<div |
|||
v-if="!isHome" |
|||
text-sm |
|||
px-2 |
|||
hover:rounded-md |
|||
hover:bg-gray-2 |
|||
hover:cursor-pointer |
|||
text="hover:hover" |
|||
title="返回上一页" |
|||
@click="back" |
|||
> |
|||
🏠 |
|||
</div> |
|||
<div text-sm px-2 hover:rounded-md hover:bg-gray-2 hover:cursor-pointer text="hover:hover" @click="onClickAbout">关于</div> |
|||
</div> |
|||
<div |
|||
relative |
|||
h="30px" |
|||
leading="29px" |
|||
pr="137px" |
|||
:style="{ paddingRight: isFullScreen ? '0' : '' }" |
|||
select-none |
|||
border-b="1px solid #E5E5E5" |
|||
bg="#F8F8F8" |
|||
> |
|||
<div absolute top-0 right-0 bottom-0 left-0 style="-webkit-app-region: drag"></div> |
|||
<div h-full px-2 flex items-center gap-1 justify-between> |
|||
<div flex items-center gap-1> |
|||
<img w="16px" h="16px" :src="icon" /> |
|||
<div relative h-full inline-flex items-center text-sm>{{ config.app_title }}</div> |
|||
<div relative class="list"> |
|||
<div class="item" @click="onClickMenu">{{ t("caidan") }}</div> |
|||
</div> |
|||
</div> |
|||
<div float-right h-full flex items-center relative style="-webkit-app-region: no-drag"> |
|||
<div |
|||
v-if="!isHome" |
|||
text-sm |
|||
px-2 |
|||
hover:rounded-md |
|||
hover:bg-gray-2 |
|||
hover:cursor-pointer |
|||
text="hover:hover" |
|||
title="返回上一页" |
|||
@click="back" |
|||
> |
|||
🏠 |
|||
</div> |
|||
<div text-sm px-2 hover:rounded-md hover:bg-gray-2 hover:cursor-pointer text="hover:hover" @click="onClickAbout">关于</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import icon from "@res/icon.png" |
|||
import config from "config" |
|||
import { PopupMenu } from "@/bridge/PopupMenu" |
|||
import { usePlatForm } from "common/usePlatform" |
|||
import icon from "@res/icon.png" |
|||
import config from "config" |
|||
import { PopupMenu } from "@/bridge/PopupMenu" |
|||
import { usePlatForm } from "common/usePlatform" |
|||
|
|||
const { PlatForm } = usePlatForm() |
|||
const { PlatForm } = usePlatForm() |
|||
|
|||
const router = useRouter() |
|||
const route = useRoute() |
|||
const isFullScreen = ref(false) |
|||
const router = useRouter() |
|||
const route = useRoute() |
|||
const isFullScreen = ref(false) |
|||
|
|||
onBeforeMount(async () => { |
|||
onBeforeMount(async () => { |
|||
isFullScreen.value = await PlatForm.isFullScreen() |
|||
}) |
|||
}) |
|||
|
|||
const isHome = computed(() => { |
|||
const isHome = computed(() => { |
|||
if (route.fullPath === "/") { |
|||
return true |
|||
return true |
|||
} |
|||
return false |
|||
}) |
|||
}) |
|||
|
|||
function back() { |
|||
function back() { |
|||
router.push("/") |
|||
} |
|||
const { t } = useI18n() |
|||
const onClickMenu = e => { |
|||
} |
|||
const { t } = useI18n() |
|||
const onClickMenu = e => { |
|||
const menu = new PopupMenu([ |
|||
{ |
|||
label: isFullScreen.value ? t("qu-xiao-quan-ping") : t("quan-ping"), |
|||
async click() { |
|||
await PlatForm.toggleFullScreen() |
|||
isFullScreen.value = !isFullScreen.value |
|||
}, |
|||
{ |
|||
label: isFullScreen.value ? t("qu-xiao-quan-ping") : t("quan-ping"), |
|||
async click() { |
|||
await PlatForm.toggleFullScreen() |
|||
isFullScreen.value = !isFullScreen.value |
|||
}, |
|||
{ |
|||
label: t("qie-huan-kai-fa-zhe-gong-ju"), |
|||
async click() { |
|||
PlatForm.toggleDevTools() |
|||
}, |
|||
}, |
|||
{ |
|||
label: t("qie-huan-kai-fa-zhe-gong-ju"), |
|||
async click() { |
|||
PlatForm.toggleDevTools() |
|||
}, |
|||
}, |
|||
]) |
|||
const obj = e.target.getBoundingClientRect() |
|||
menu.show({ x: ~~obj.x, y: ~~(obj.y + obj.height) }) |
|||
} |
|||
} |
|||
|
|||
const onClickAbout = () => { |
|||
const onClickAbout = () => { |
|||
PlatForm.showAbout() |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.list { |
|||
.list { |
|||
@apply: flex gap="5px"; |
|||
-webkit-app-region: no-drag; |
|||
|
|||
.item { |
|||
@apply: text-sm px-2 hover:rounded-md hover:bg-gray-2 hover:cursor-pointer text="hover:hover"; |
|||
@apply: text-sm px-2 hover:rounded-md hover:bg-gray-2 hover:cursor-pointer text="hover:hover"; |
|||
} |
|||
} |
|||
} |
|||
</style> |
|||
|
@ -1,13 +1,13 @@ |
|||
<script setup lang="ts"> |
|||
import { reactive } from "vue" |
|||
import { reactive } from "vue" |
|||
|
|||
const versions = reactive({ ...window.electron.process.versions }) |
|||
const versions = reactive({ ...window.electron.process.versions }) |
|||
</script> |
|||
|
|||
<template> |
|||
<ul class="versions"> |
|||
<li class="electron-version">Electron v{{ versions.electron }}</li> |
|||
<li class="chrome-version">Chromium v{{ versions.chrome }}</li> |
|||
<li class="node-version">Node v{{ versions.node }}</li> |
|||
</ul> |
|||
<ul class="versions"> |
|||
<li class="electron-version">Electron v{{ versions.electron }}</li> |
|||
<li class="chrome-version">Chromium v{{ versions.chrome }}</li> |
|||
<li class="node-version">Node v{{ versions.node }}</li> |
|||
</ul> |
|||
</template> |
|||
|
@ -1,3 +1,3 @@ |
|||
export function useTest() { |
|||
console.log("test") |
|||
console.log("test") |
|||
} |
|||
|
@ -1,15 +1,15 @@ |
|||
<script lang="ts" setup> |
|||
import Simplebar from "simplebar-vue" |
|||
import Simplebar from "simplebar-vue" |
|||
</script> |
|||
|
|||
<template> |
|||
<Simplebar h-full> |
|||
<RouterView></RouterView> |
|||
</Simplebar> |
|||
<Simplebar h-full> |
|||
<RouterView></RouterView> |
|||
</Simplebar> |
|||
</template> |
|||
|
|||
<style scoped> |
|||
:deep(.simplebar-content) { |
|||
:deep(.simplebar-content) { |
|||
height: 100%; |
|||
} |
|||
} |
|||
</style> |
|||
|
@ -1,3 +1,3 @@ |
|||
<template> |
|||
<div @click="$router.back()">Not Found</div> |
|||
<div @click="$router.back()">Not Found</div> |
|||
</template> |
|||
|
@ -1,14 +1,14 @@ |
|||
<script setup lang="ts"> |
|||
defineOptions({ |
|||
defineOptions({ |
|||
title: "观山", |
|||
bg: "ty", |
|||
}) |
|||
}) |
|||
</script> |
|||
|
|||
<template> |
|||
<div> |
|||
<input type="text" /> |
|||
<input type="text" /> |
|||
<input type="text" /> |
|||
</div> |
|||
<div> |
|||
<input type="text" /> |
|||
<input type="text" /> |
|||
<input type="text" /> |
|||
</div> |
|||
</template> |
|||
|
@ -1,37 +1,37 @@ |
|||
<script setup lang="ts"> |
|||
definePage({ |
|||
definePage({ |
|||
name: "about", |
|||
meta: { |
|||
title: "听雨", |
|||
bg: "gs", |
|||
title: "听雨", |
|||
bg: "gs", |
|||
}, |
|||
}) |
|||
}) |
|||
|
|||
// const activeTab = ref(0) |
|||
// const TopMenu = computed<any[]>(() => { |
|||
// return [ |
|||
// { key: 0, title: "sada", url: "/setting" }, |
|||
// { key: 1, title: "sdas", url: "/setting/editor" }, |
|||
// { key: 2, title: "asdas", url: "/setting/update" }, |
|||
// ] |
|||
// }) |
|||
// const route = useRoute() |
|||
// watch( |
|||
// () => route, |
|||
// route => { |
|||
// for (let i = 0; i < TopMenu.value.length; i++) { |
|||
// const element = TopMenu.value[i] |
|||
// if (route.path.startsWith(element.url)) { |
|||
// activeTab.value = element.key |
|||
// } |
|||
// } |
|||
// }, |
|||
// { immediate: true }, |
|||
// ) |
|||
// const activeTab = ref(0) |
|||
// const TopMenu = computed<any[]>(() => { |
|||
// return [ |
|||
// { key: 0, title: "sada", url: "/setting" }, |
|||
// { key: 1, title: "sdas", url: "/setting/editor" }, |
|||
// { key: 2, title: "asdas", url: "/setting/update" }, |
|||
// ] |
|||
// }) |
|||
// const route = useRoute() |
|||
// watch( |
|||
// () => route, |
|||
// route => { |
|||
// for (let i = 0; i < TopMenu.value.length; i++) { |
|||
// const element = TopMenu.value[i] |
|||
// if (route.path.startsWith(element.url)) { |
|||
// activeTab.value = element.key |
|||
// } |
|||
// } |
|||
// }, |
|||
// { immediate: true }, |
|||
// ) |
|||
</script> |
|||
<template> |
|||
<div> |
|||
about |
|||
<!-- <HTab v-model="activeTab" :list="TopMenu"></HTab> --> |
|||
</div> |
|||
<div> |
|||
about |
|||
<!-- <HTab v-model="activeTab" :list="TopMenu"></HTab> --> |
|||
</div> |
|||
</template> |
|||
|
@ -1,143 +1,143 @@ |
|||
<script setup lang="ts"> |
|||
import Simplebar from "simplebar-vue" |
|||
import { getAssetsFile } from "@/utils" |
|||
import Simplebar from "simplebar-vue" |
|||
import { getAssetsFile } from "@/utils" |
|||
|
|||
const allModules: Record<string, any> = import.meta.glob("./_ui/**/*.vue", { eager: true }) |
|||
let allApp: any[] = [] |
|||
Object.keys(allModules).forEach(key => { |
|||
const allModules: Record<string, any> = import.meta.glob("./_ui/**/*.vue", { eager: true }) |
|||
let allApp: any[] = [] |
|||
Object.keys(allModules).forEach(key => { |
|||
// let [, p] = key.match("./_ui/(.*?).vue")! |
|||
// p = p.replace(/\.vue$/, "") |
|||
const m = allModules[key]?.default || allModules[key] |
|||
allApp.push({ |
|||
label: m.title, |
|||
bg: m.bg, |
|||
_sort: m.index ?? 0, |
|||
comp: m, |
|||
label: m.title, |
|||
bg: m.bg, |
|||
_sort: m.index ?? 0, |
|||
comp: m, |
|||
}) |
|||
}) |
|||
allApp = allApp.sort((a, b) => (a.index - b.index <= 0 ? 1 : -1)) |
|||
}) |
|||
allApp = allApp.sort((a, b) => (a.index - b.index <= 0 ? 1 : -1)) |
|||
|
|||
const active = ref(0) |
|||
// const allApp = [ |
|||
// { label: "浏览器", comp: defineAsyncComponent(() => import("./_ui/Browser.vue")) }, |
|||
// { label: "观山", bg: "gs", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "听雨", bg: "ty", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "赏月", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "抚琴", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "望云", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "踏雪", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "卧松", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "泛舟", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "弈星", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "垂钓", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "采菊", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "倚栏", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "望霞", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "枕风", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "沐泉", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "汲露", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "步竹", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "剪烛", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "拾阶", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "撷兰", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "访柳", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "谒梅", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "拨荷", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "望鹤", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// ] |
|||
const active = ref(0) |
|||
// const allApp = [ |
|||
// { label: "浏览器", comp: defineAsyncComponent(() => import("./_ui/Browser.vue")) }, |
|||
// { label: "观山", bg: "gs", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "听雨", bg: "ty", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "赏月", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "抚琴", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "望云", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "踏雪", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "卧松", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "泛舟", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "弈星", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "垂钓", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "采菊", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "倚栏", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "望霞", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "枕风", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "沐泉", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "汲露", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "步竹", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "剪烛", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "拾阶", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "撷兰", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "访柳", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "谒梅", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "拨荷", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// { label: "望鹤", comp: defineAsyncComponent(() => import("./_ui/App.vue")) }, |
|||
// ] |
|||
|
|||
const activeBg = computed(() => { |
|||
const activeBg = computed(() => { |
|||
if (active.value === undefined) return "" |
|||
const value = allApp[active.value]?.bg |
|||
return value ? getAssetsFile(`@/assets/images/home/${value}.png`) : "" |
|||
}) |
|||
}) |
|||
|
|||
function onClick(index: number) { |
|||
function onClick(index: number) { |
|||
active.value = index |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<template> |
|||
<div h-full flex> |
|||
<div w="100px" h-full relative max-w="200px" min-w="80px"> |
|||
<Simplebar h-full> |
|||
<div |
|||
v-for="(app, index) in allApp" |
|||
:key="index" |
|||
p="8px 10px" |
|||
text="12px" |
|||
border |
|||
border-b |
|||
h="30px" |
|||
cursor="pointer" |
|||
hover:bg-gray-50 |
|||
class="item" |
|||
transition-all |
|||
:class="{ active: active === index }" |
|||
@click="onClick(index)" |
|||
> |
|||
<div class="text" transition-all position="absolute" left="10px">{{ app.label }}</div> |
|||
</div> |
|||
</Simplebar> |
|||
<!-- <AdjustLine></AdjustLine> --> |
|||
</div> |
|||
<div class="content" relative b-l="1px solid #E5E5E5" flex-1 w-0 overflow-auto flex flex-col> |
|||
<div v-if="activeBg" class="bg" :style="{ backgroundImage: activeBg ? `url(${activeBg})` : '' }"></div> |
|||
<div @click="$router.push('/about')">关于</div> |
|||
<component :is="allApp[active].comp" v-if="allApp[active]"></component> |
|||
<div h-full flex> |
|||
<div w="100px" h-full relative max-w="200px" min-w="80px"> |
|||
<Simplebar h-full> |
|||
<div |
|||
v-for="(app, index) in allApp" |
|||
:key="index" |
|||
p="8px 10px" |
|||
text="12px" |
|||
border |
|||
border-b |
|||
h="30px" |
|||
cursor="pointer" |
|||
hover:bg-gray-50 |
|||
class="item" |
|||
transition-all |
|||
:class="{ active: active === index }" |
|||
@click="onClick(index)" |
|||
> |
|||
<div class="text" transition-all position="absolute" left="10px">{{ app.label }}</div> |
|||
</div> |
|||
</Simplebar> |
|||
<!-- <AdjustLine></AdjustLine> --> |
|||
</div> |
|||
<div class="content" relative b-l="1px solid #E5E5E5" flex-1 w-0 overflow-auto flex flex-col> |
|||
<div v-if="activeBg" class="bg" :style="{ backgroundImage: activeBg ? `url(${activeBg})` : '' }"></div> |
|||
<div @click="$router.push('/about')">关于</div> |
|||
<component :is="allApp[active].comp" v-if="allApp[active]"></component> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<style lang="scss" scoped> |
|||
.content { |
|||
.content { |
|||
.bg { |
|||
position: absolute; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
top: 0; |
|||
background-size: cover; |
|||
background-position: center; |
|||
background-repeat: no-repeat; |
|||
z-index: -1; |
|||
opacity: 0.1; |
|||
// blur(4px) |
|||
filter: brightness(1); |
|||
position: absolute; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
top: 0; |
|||
background-size: cover; |
|||
background-position: center; |
|||
background-repeat: no-repeat; |
|||
z-index: -1; |
|||
opacity: 0.1; |
|||
// blur(4px) |
|||
filter: brightness(1); |
|||
} |
|||
} |
|||
.item { |
|||
} |
|||
.item { |
|||
position: relative; |
|||
&::before { |
|||
content: ""; |
|||
position: absolute; |
|||
left: 0; |
|||
top: 0; |
|||
height: 100%; |
|||
width: 6px; |
|||
background-color: #f3f4f6; |
|||
transition: all linear 300ms; |
|||
content: ""; |
|||
position: absolute; |
|||
left: 0; |
|||
top: 0; |
|||
height: 100%; |
|||
width: 6px; |
|||
background-color: #f3f4f6; |
|||
transition: all linear 300ms; |
|||
} |
|||
&:hover { |
|||
&::before { |
|||
width: 30px; |
|||
} |
|||
.text { |
|||
left: 20px; |
|||
} |
|||
&::before { |
|||
width: 30px; |
|||
} |
|||
.text { |
|||
left: 20px; |
|||
} |
|||
} |
|||
&.active { |
|||
@apply: text-black; |
|||
&::before { |
|||
width: 100%; |
|||
} |
|||
.text { |
|||
left: 50%; |
|||
transform: translateX(-50%); |
|||
} |
|||
@apply: text-black; |
|||
&::before { |
|||
width: 100%; |
|||
} |
|||
.text { |
|||
left: 50%; |
|||
transform: translateX(-50%); |
|||
} |
|||
} |
|||
.text { |
|||
transition-duration: 300ms; |
|||
transition-duration: 300ms; |
|||
} |
|||
} |
|||
} |
|||
</style> |
|||
|
@ -1,31 +1,31 @@ |
|||
<script setup lang="ts"> |
|||
definePage({ |
|||
definePage({ |
|||
meta: { |
|||
home: true, |
|||
home: true, |
|||
}, |
|||
}) |
|||
}) |
|||
|
|||
const state = reactive({ |
|||
const state = reactive({ |
|||
content: "", |
|||
name: "aaa.ts", |
|||
}) |
|||
}) |
|||
</script> |
|||
|
|||
<template> |
|||
<div class="locale-changer"> |
|||
<select v-model="$i18n.locale"> |
|||
<option v-for="locale in $i18n.availableLocales" :key="`locale-${locale}`" :value="locale">{{ locale }}</option> |
|||
</select> |
|||
</div> |
|||
<div @click="$router.push('/about')"> |
|||
<span>跳转 about</span> |
|||
</div> |
|||
<div @click="$router.push('/browser')"> |
|||
<span>跳转 browser</span> |
|||
</div> |
|||
<input v-model="state.content" /> |
|||
<CodeEditor v-model="state.content" style="height: 400px" :name="state.name" placeholder="输入代码"></CodeEditor> |
|||
<div v-for="i in 1000" :key="i"> |
|||
<span>{{ i }}</span> |
|||
</div> |
|||
<div class="locale-changer"> |
|||
<select v-model="$i18n.locale"> |
|||
<option v-for="locale in $i18n.availableLocales" :key="`locale-${locale}`" :value="locale">{{ locale }}</option> |
|||
</select> |
|||
</div> |
|||
<div @click="$router.push('/about')"> |
|||
<span>跳转 about</span> |
|||
</div> |
|||
<div @click="$router.push('/browser')"> |
|||
<span>跳转 browser</span> |
|||
</div> |
|||
<input v-model="state.content" /> |
|||
<CodeEditor v-model="state.content" style="height: 400px" :name="state.name" placeholder="输入代码"></CodeEditor> |
|||
<div v-for="i in 1000" :key="i"> |
|||
<span>{{ i }}</span> |
|||
</div> |
|||
</template> |
|||
|
@ -1,5 +1,5 @@ |
|||
import type { AttributifyAttributes } from "@unocss/preset-attributify" |
|||
|
|||
declare module "@vue/runtime-dom" { |
|||
interface HTMLAttributes extends AttributifyAttributes {} |
|||
interface HTMLAttributes extends AttributifyAttributes {} |
|||
} |
|||
|
@ -1,21 +1,20 @@ |
|||
|
|||
type Api = { |
|||
call: (command: string, ...args: any[]) => Promise<any> |
|||
callLong: (command: string, ...args: any[]) => Promise<any> |
|||
callSync: (command: string, ...args: any[]) => any |
|||
send: (command: string, ...argu: any[]) => any |
|||
sendSync: (command: string, ...argu: any[]) => any |
|||
on: <T extends string>(command: T, cb: (event: IpcRendererEvent, ...args: any[]) => void) => () => void |
|||
once: (command: string, cb: (event: IpcRendererEvent, ...args: any[]) => void) => () => void |
|||
off: (command: string, cb: (event: IpcRendererEvent, ...args: any[]) => void) => void |
|||
offAll: (command: string) => void |
|||
popupMenu: (options: IPopupMenuOption) => void |
|||
call: (command: string, ...args: any[]) => Promise<any> |
|||
callLong: (command: string, ...args: any[]) => Promise<any> |
|||
callSync: (command: string, ...args: any[]) => any |
|||
send: (command: string, ...argu: any[]) => any |
|||
sendSync: (command: string, ...argu: any[]) => any |
|||
on: <T extends string>(command: T, cb: (event: IpcRendererEvent, ...args: any[]) => void) => () => void |
|||
once: (command: string, cb: (event: IpcRendererEvent, ...args: any[]) => void) => () => void |
|||
off: (command: string, cb: (event: IpcRendererEvent, ...args: any[]) => void) => void |
|||
offAll: (command: string) => void |
|||
popupMenu: (options: IPopupMenuOption) => void |
|||
} |
|||
|
|||
declare const electron: typeof import("@electron-toolkit/preload").electronAPI |
|||
declare const api: Api |
|||
|
|||
interface Window { |
|||
electron: typeof import("@electron-toolkit/preload").electronAPI |
|||
api: Api |
|||
electron: typeof import("@electron-toolkit/preload").electronAPI |
|||
api: Api |
|||
} |
|||
|
@ -1,12 +1,12 @@ |
|||
import type { PopupOptions } from "electron" |
|||
|
|||
export interface IMenuItemOption extends Electron.MenuItemConstructorOptions { |
|||
// 参见:https://www.electronjs.org/docs/api/menu-item
|
|||
_click_evt?: string |
|||
// 参见:https://www.electronjs.org/docs/api/menu-item
|
|||
_click_evt?: string |
|||
} |
|||
|
|||
export interface IPopupMenuOption { |
|||
menu_id: string |
|||
items: IMenuItemOption[] |
|||
popupOptions?: PopupOptions |
|||
menu_id: string |
|||
items: IMenuItemOption[] |
|||
popupOptions?: PopupOptions |
|||
} |
|||
|
@ -1,4 +1,4 @@ |
|||
import { defineConfig } from "unplugin-vue-macros" |
|||
export default defineConfig({ |
|||
// 选项
|
|||
// 选项
|
|||
}) |
|||
|
@ -1,15 +1,11 @@ |
|||
|
|||
插件化: |
|||
|
|||
https://rubickcenter.github.io/docs/core/index.html#%E5%9F%BA%E4%BA%8E-browserview-%E5%AE%9E%E7%8E%B0%E6%8F%92%E4%BB%B6%E5%8C%96%E8%83%BD%E5%8A%9B |
|||
|
|||
|
|||
electron+vue虚拟桌面开发遇坑之透明窗口鼠标穿透 |
|||
|
|||
|
|||
https://blog.csdn.net/weixin_42421494/article/details/102800491 |
|||
|
|||
|
|||
截图 |
|||
|
|||
https://zhuanlan.zhihu.com/p/46043613?from_voters_page=true |
|||
|
Loading…
Reference in new issue