Browse Source

fix bug

main
npmrun 2 months ago
parent
commit
bb2a5cdb39
  1. 2
      .vscode/settings.json
  2. 57
      src/main/commands/BasicCommand.ts
  3. 30
      src/renderer/index.html
  4. 292
      src/renderer/src/components/AdjustLine.vue
  5. 39
      src/renderer/src/components/NavBar.vue
  6. 387
      src/renderer/src/pages/index.vue

2
.vscode/settings.json

@ -1,6 +1,6 @@
{
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
"editor.defaultFormatter": "vscode.typescript-language-features"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"

57
src/main/commands/BasicCommand.ts

@ -1,5 +1,58 @@
import { app, dialog } from "electron"
import { inject } from "inversify"
import WindowManager from "main/modules/window-manager"
export default class BasicCommand {
log() {
console.log("1231")
constructor(@inject(WindowManager) private _WindowManager: WindowManager) {
//
}
toggleDevTools() {
const focusedWindow = this._WindowManager.getFocusWindow()
if (focusedWindow) {
// @ts-ignore ...
focusedWindow.toggleDevTools()
}
}
fullscreen() {
const focusedWindow = this._WindowManager.getFocusWindow()
const isFullScreen = focusedWindow!.isFullScreen()
focusedWindow!.setFullScreen(!isFullScreen)
return !isFullScreen
}
isFullscreen() {
const focusedWindow = this._WindowManager.getFocusWindow()
return focusedWindow!.isFullScreen()
}
relunch() {
app.relaunch()
app.exit()
}
reload() {
const focusedWindow = this._WindowManager.getFocusWindow()
// 重载之后, 刷新并关闭所有的次要窗体
if (this._WindowManager.length() > 1 && focusedWindow && focusedWindow.$$opts!.name === this._WindowManager.mainInfo.name) {
const choice = dialog.showMessageBoxSync(focusedWindow, {
type: "question",
buttons: ["取消", "是的,继续", "不,算了"],
title: "警告",
defaultId: 2,
cancelId: 0,
message: "警告",
detail: "重载主窗口将关闭所有子窗口,是否继续",
})
if (choice == 1) {
this._WindowManager.getWndows().forEach(win => {
if (win.$$opts!.name !== this._WindowManager.mainInfo.name) {
win.close()
}
})
} else {
return
}
}
focusedWindow!.reload()
}
}

30
src/renderer/index.html

@ -1,17 +1,19 @@
<!doctype html>
<html>
<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'; script-src 'self' api:; style-src 'self' 'unsafe-inline'; img-src 'self' data:;"
/>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
<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';
script-src 'self' api:;
style-src 'self' 'unsafe-inline';
img-src 'self' data: *;" />
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

292
src/renderer/src/components/AdjustLine.vue

@ -1,17 +1,29 @@
<template>
<div class="adjust-line" :class="['adjust-line__' + direction]" ref="adjustLineEL"></div>
<div ref="adjustLineEL" :class="['adjust-line', `adjust-line--${direction}`, { 'adjust-line--dragging': isDragging }]">
<div class="adjust-line__handle">
<div class="adjust-line__grip">
<span class="grip-line"></span>
<span class="grip-line"></span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { nextTick, onMounted, ref, watch } from "vue"
import { nextTick, onMounted, ref, watch, computed, onBeforeUnmount, onErrorCaptured } from "vue"
import { useDebounceFn } from "@vueuse/core"
const adjustLineEL = ref<HTMLElement>()
type IProps = {
//
type Direction = "left" | "right" | "top" | "bottom"
// Props
interface AdjustLineProps {
/**
* 所在方向 'left' | 'right' | 'top' | 'bottom'
*/
direction?: "left" | "right" | "top" | "bottom"
direction?: Direction
/**
* 需要调整的元素
*/
@ -24,14 +36,86 @@ type IProps = {
* 唯一ID
*/
mid?: string
minSize?: number
maxSize?: number
defaultSize?: number
onChange?: (size: number) => void
}
const props = withDefaults(defineProps<IProps>(), {
const props = withDefaults(defineProps<AdjustLineProps>(), {
direction: "right",
minSize: 100,
maxSize: 800,
})
//
const emit = defineEmits<{
(e: "resize", size: number): void
(e: "resizeStart"): void
(e: "resizeEnd", size: number): void
}>()
let curTarget: HTMLElement | undefined | null
const isDragging = ref(false)
const currentSize = ref(props.defaultSize || 0)
// 使computed
const isHorizontal = computed(() => props.direction === "left" || props.direction === "right")
// 使computed
const cursorStyle = computed(() => (isHorizontal.value ? "ew-resize" : "ns-resize"))
// localStorage
const storageKey = computed(() => `adjust-line-${props.mid}`)
function saveSize(size: number) {
if (props.mid) {
try {
localStorage.setItem(storageKey.value, String(size))
} catch (error) {
console.warn("Failed to save size to localStorage:", error)
}
}
}
function loadSavedSize(): number | null {
if (props.mid) {
try {
const saved = localStorage.getItem(storageKey.value)
return saved ? Number(saved) : null
} catch (error) {
console.warn("Failed to load size from localStorage:", error)
return null
}
}
return null
}
// 使resize
const emitResize = useDebounceFn((size: number) => {
emit("resize", size)
}, 16)
// 使ResizeObserver
const observeResize = () => {
if (!adjustLineEL.value) return
const observer = new ResizeObserver(() => {
if (curTarget) {
const size = isHorizontal.value ? curTarget.clientWidth : curTarget.clientHeight
currentSize.value = size
emitResize(size)
}
})
observer.observe(adjustLineEL.value)
onBeforeUnmount(() => {
observer.disconnect()
})
}
onMounted(async () => {
await nextTick()
if (!props.target) {
@ -51,6 +135,7 @@ onMounted(async () => {
}
},
)
observeResize()
})
function handle(target: HTMLElement) {
@ -231,49 +316,198 @@ function handle(target: HTMLElement) {
}
}
}
function handleDrag(e: MouseEvent, target: HTMLElement) {
const startPos = isHorizontal.value ? e.clientX : e.clientY
const startSize = isHorizontal.value ? target.clientWidth : target.clientHeight
const handleMouseMove = (e: MouseEvent) => {
const currentPos = isHorizontal.value ? e.clientX : e.clientY
const diff = props.direction === "right" || props.direction === "bottom" ? startPos - currentPos : currentPos - startPos
let newSize = startSize - diff
//
newSize = Math.max(props.minSize, Math.min(props.maxSize, newSize))
//
if (isHorizontal.value) {
target.style.width = `${newSize}px`
} else {
target.style.height = `${newSize}px`
}
currentSize.value = newSize
emit("resize", newSize)
}
const handleMouseUp = () => {
document.removeEventListener("mousemove", handleMouseMove)
document.removeEventListener("mouseup", handleMouseUp)
document.body.style.userSelect = ""
isDragging.value = false
saveSize(currentSize.value)
emit("resizeEnd", currentSize.value)
}
document.addEventListener("mousemove", handleMouseMove)
document.addEventListener("mouseup", handleMouseUp)
document.body.style.userSelect = "none"
isDragging.value = true
emit("resizeStart")
}
const debug = {
log: (...args: any[]) => {
if (process.env.NODE_ENV === "development") {
console.log("[AdjustLine]", ...args)
}
},
error: (...args: any[]) => {
console.error("[AdjustLine]", ...args)
},
}
function handleError(error: Error, context: string) {
debug.error(`Error in ${context}:`, error)
//
}
//
onErrorCaptured((err, instance, info) => {
handleError(err as Error, info)
return false
})
</script>
<style lang="scss" scoped>
.adjust-line {
position: absolute;
z-index: 999;
transition: background-color 0.5s ease;
&:hover,
&:active {
background: #1976d2;
&__handle {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
background: transparent;
transition: all 0.2s ease;
}
&__grip {
display: flex;
gap: 3px;
opacity: 0;
transition: opacity 0.2s;
.adjust-line:hover &,
.adjust-line--dragging & {
opacity: 1;
}
}
.grip-line {
background-color: #999;
border-radius: 1px;
.adjust-line:hover &,
.adjust-line--dragging & {
background-color: #666;
}
}
&__left {
left: -2px;
// 线
&--left,
&--right {
top: 0;
bottom: 0;
width: 4px;
cursor: ew-resize;
width: 10px; //
cursor: col-resize;
.adjust-line__handle {
left: 0;
top: 0;
bottom: 0;
width: 100%;
}
.adjust-line__grip {
flex-direction: column;
}
.grip-line {
width: 2px;
height: 16px;
}
&:hover .adjust-line__handle {
background-color: rgba(0, 0, 0, 0.05);
}
}
&__top {
top: -2px;
// 线
&--top,
&--bottom {
left: 0;
right: 0;
height: 4px;
cursor: n-resize;
height: 10px; //
cursor: row-resize;
.adjust-line__handle {
top: 0;
left: 0;
right: 0;
height: 100%;
}
.adjust-line__grip {
flex-direction: row;
}
.grip-line {
width: 16px;
height: 2px;
}
&:hover .adjust-line__handle {
background-color: rgba(0, 0, 0, 0.05);
}
}
&__bottom {
bottom: -2px;
left: 0;
right: 0;
height: 4px;
cursor: n-resize;
//
&--left {
left: -5px;
}
&--right {
right: -5px;
}
&--top {
top: -5px;
}
&--bottom {
bottom: -5px;
}
&__right {
right: -2px;
top: 0;
bottom: 0;
width: 4px;
cursor: ew-resize;
//
&--dragging {
&::after {
content: "";
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: -1;
cursor: inherit;
}
.adjust-line__handle {
background-color: rgba(0, 0, 0, 0.08);
}
.grip-line {
background-color: #666;
}
}
}
</style>

39
src/renderer/src/components/NavBar.vue

@ -1,5 +1,14 @@
<template>
<div relative h="30px" leading="29px" pr="137px" select-none border-b="1px solid #E5E5E5" bg="#F8F8F8">
<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>
@ -21,17 +30,39 @@ import icon from "@res/icon.png"
import config from "config"
import { PopupMenu } from "@/bridge/PopupMenu"
const isFullScreen = ref(false)
onBeforeMount(async () => {
isFullScreen.value = await api.call("BasicCommand.isFullscreen")
})
const onClickMenu = () => {
const menu = new PopupMenu([
{
label: "关于",
click() {
fetch("api://fuck/BasicService/showAbout")
label: "全屏",
async click() {
isFullScreen.value = await api.call("BasicCommand.fullscreen")
},
},
{
label: "切换开发者工具",
async click() {
isFullScreen.value = await api.call("BasicCommand.toggleDevTools")
},
},
{
type: "separator",
},
{
label: "重载",
click() {
api.call("BasicCommand.reload")
},
},
{
label: "重启",
click() {
api.call("BasicCommand.relunch")
},
},
])
menu.show()
}

387
src/renderer/src/pages/index.vue

@ -1,5 +1,5 @@
<script setup lang="ts">
import { onBeforeMount, onBeforeUnmount, onMounted, ref, useTemplateRef } from "vue"
import { onBeforeMount, onBeforeUnmount, onMounted, ref, useTemplateRef, nextTick } from "vue"
import { PopupMenu } from "@/bridge/PopupMenu"
// const PlaceHolderRef = useTemplateRef("PlaceHolder")
@ -25,6 +25,7 @@ const { stop } = useResizeObserver(PlaceHolder, () => {
api.call("TabsCommand.bindElement", rect)
}
})
onBeforeUnmount(() => {
stop()
})
@ -89,8 +90,19 @@ function handleTabContextMenu(_, index) {
menu.show()
}
function changeTab(_, index) {
api.call("TabsCommand.setActive", index)
function scrollTabIntoView(index: number) {
nextTick(() => {
const tabList = document.querySelector('.tab-list')
const tabItems = tabList?.querySelectorAll('.tab-item')
if (tabList && tabItems && tabItems[index]) {
tabItems[index].scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' })
}
})
}
async function changeTab(_, index) {
await api.call("TabsCommand.setActive", index)
scrollTabIntoView(index)
}
function addTabInput() {
@ -102,8 +114,10 @@ function addTabInput() {
}
}
}
function addTab() {
api.call("TabsCommand.add", "about:blank")
async function addTab() {
await api.call("TabsCommand.add", "about:blank")
scrollTabIntoView(list.value.length - 1)
}
async function closeTab(_, index) {
@ -135,57 +149,330 @@ function onClickDevTool() {
<AdjustLine></AdjustLine>
</div>
<div b-l="1px solid #E5E5E5" flex-1 w-0 overflow-auto flex flex-col>
<div h="100px" flex flex-col b-b="1px solid #E5E5E5">
<div flex gap-1 my-1 px-1 w-full>
<div
v-for="(item, index) in list"
:key="index"
p-1
b-b="1px solid gray"
b-l="1px solid gray"
b-r="1px solid gray"
:b-t="item.isActive ? '1px solid red' : '1px solid gray'"
flex
flex-1
w-0
items-center
cursor="pointer"
gap="5px"
max-w="200px"
@contextmenu="handleTabContextMenu(item, index)"
@click="changeTab(item, index)"
>
<div flex-1 w-0 line-1 text-normal>{{ item.title || "加载中..." }}</div>
<span p-1 rounded hover="bg-gray-2 text-hover" @click.stop="closeTab(item, index)">X</span>
<!-- Tab栏 -->
<div h="100px" flex flex-col b-b="1px solid var(--border-color)" class="tab-container">
<!-- Tab列表 -->
<div class="tab-list-container">
<div class="tab-list">
<div
v-for="(item, index) in list"
:key="index"
:class="{
'tab-item': true,
'active': item.isActive
}"
@contextmenu="handleTabContextMenu(item, index)"
@click="changeTab(item, index)"
>
<div class="tab-content">
<!-- 网站图标 -->
<img
v-if="item.favicons?.length"
:src="item.favicons[0]"
class="tab-icon"
alt=""
/>
<div v-else class="tab-icon-placeholder"></div>
<!-- 标题 -->
<div class="tab-title">{{ item.title || "加载中..." }}</div>
<!-- 关闭按钮 -->
<div class="tab-close" @click.stop="closeTab(item, index)">
<svg width="16" height="16" viewBox="0 0 16 16">
<path d="M12.81 4.36l-1.17-1.17L8 6.83 4.36 3.19 3.19 4.36 6.83 8l-3.64 3.64 1.17 1.17L8 9.17l3.64 3.64 1.17-1.17L9.17 8z"
fill="currentColor"/>
</svg>
</div>
</div>
</div>
</div>
<div
p-1
b-b="1px solid gray"
b-l="1px solid gray"
b-r="1px solid gray"
b-t="1px solid gray"
flex
items-center
cursor="pointer"
gap="5px"
hover="bg-gray-2 text-hover"
>
<span p-1 rounded @click.stop="addTab()">+</span>
<!-- 新建标签页按钮移到容器外部 -->
<div class="new-tab-button-container">
<div class="new-tab-button" @click.stop="addTab()">
<svg width="20" height="20" viewBox="0 0 20 20">
<path d="M10 4v12M4 10h12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
</svg>
</div>
</div>
</div>
<div mx="5px" overflow="auto" flex-1 h-0 flex items-center gap-x="5px">
<div flex-1 w-0 h="35px" px-3 rounded="35px" b="1px solid gray-4" flex items-center>
<input v-model="curUrl" placeholder="输入点什么" w-full text="16px" b-0 leading="25px" outline-0 type="text" />
</div>
<div inline-block hover="bg-gray-2 text-hover" px-1 py-1 rounded cursor="pointer" @click="addTabInput()">
<button text="14px" bg-transparent b-0 cursor="pointer">前往</button>
</div>
<div inline-block hover="bg-gray-2 text-hover" px-1 py-1 rounded cursor="pointer" @click="onClickDevTool()">
<button text="14px" bg-transparent b-0 cursor="pointer">DevTool</button>
<!-- 地址栏 -->
<div class="address-bar">
<div class="url-input-container">
<input
v-model="curUrl"
placeholder="输入网址"
type="text"
class="url-input"
/>
</div>
<button class="action-button" @click="addTabInput()">前往</button>
<button class="action-button" @click="onClickDevTool()">DevTool</button>
</div>
</div>
<div ref="PlaceHolder" ml="1px" flex-1 h-0 flex items-center justify-center>fuck</div>
<!-- 内容区域 -->
<div ref="PlaceHolder" ml="1px" flex-1 h-0 flex items-center justify-center>
<!-- 保持原有内容 -->
</div>
</div>
</div>
</template>
<style scoped>
.tab-container {
background: var(--tab-bar-bg, #f3f3f3);
border-bottom: none;
padding-top: 4px;
height: auto;
min-height: 80px;
}
.tab-list-container {
position: relative;
display: flex;
align-items: flex-end;
height: 32px;
padding: 0;
margin-bottom: 0;
width: 100%;
overflow: auto;
}
.tab-list {
height: 32px;
display: flex;
align-items: flex-end;
gap: 0;
margin-bottom: 0;
overflow-x: auto;
overflow-y: hidden;
padding: 0 4px;
/* 隐藏滚动条但保持功能 */
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE and Edge */
&::-webkit-scrollbar {
display: none; /* Chrome, Safari and Opera */
}
}
.tab-item {
position: relative;
min-width: 160px;
max-width: 240px;
height: 29px;
margin-right: -6px;
border-radius: 6px 6px 0 0;
background: var(--tab-bg, #dee1e6);
transition: all 0.15s ease;
z-index: 1;
display: flex;
align-items: center;
flex-shrink: 0; /* 防止标签被压缩 */
cursor: pointer;
}
.tab-item::after {
content: '';
position: absolute;
right: 0;
top: 6px;
height: 16px;
width: 1px;
background: var(--tab-separator-color, #bdc1c6);
opacity: 0.3;
}
.tab-item:hover {
background: var(--tab-hover-bg, #e9ebee);
}
.tab-item.active {
background: var(--tab-active-bg, #fff);
z-index: 2;
height: 32px;
margin-bottom: -1px;
}
.tab-item.active::before {
content: '';
position: absolute;
left: 0;
right: 0;
top: 0;
height: 2px;
background: var(--primary-color, #1a73e8);
border-radius: 2px 2px 0 0;
}
.tab-content {
display: flex;
align-items: center;
padding: 0 8px;
height: 100%;
gap: 6px;
width: 100%;
}
.tab-icon {
width: 16px;
height: 16px;
flex-shrink: 0;
border-radius: 3px;
}
.tab-icon-placeholder {
width: 16px;
height: 16px;
background: #bdc1c6;
border-radius: 50%;
flex-shrink: 0;
opacity: 0.7;
}
.tab-title {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 12px;
color: var(--text-color, #5f6368);
line-height: 1.2;
padding-right: 4px;
}
.tab-item.active .tab-title {
color: var(--active-text-color, #202124);
}
.tab-close {
width: 16px;
height: 16px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
color: var(--icon-color, #5f6368);
opacity: 0;
transition: opacity 0.15s ease;
flex-shrink: 0;
}
.tab-item:hover .tab-close {
opacity: 0.6;
}
.tab-close:hover {
background: var(--close-hover-bg, rgba(0, 0, 0, 0.08));
opacity: 1;
}
.new-tab-button-container {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 32px;
background: var(--tab-bar-bg);
}
.new-tab-button {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
color: var(--icon-color, #5f6368);
transition: all 0.15s ease;
cursor: pointer;
opacity: 0.8;
background: transparent;
}
.new-tab-button:hover {
background: var(--tab-hover-bg, rgba(0, 0, 0, 0.06));
opacity: 1;
}
.address-bar {
padding: 8px 12px;
margin: 0;
background: var(--address-bar-bg, #fff);
display: flex;
align-items: center;
gap: 8px;
width: 100%;
border-top: 1px solid var(--tab-separator-color, rgba(0, 0, 0, 0.1));
}
.url-input-container {
flex: 1;
height: 36px;
background: var(--input-bg, #f1f3f4);
border-radius: 18px;
padding: 0 16px;
display: flex;
align-items: center;
margin: 0;
min-width: 0;
}
.url-input {
width: 100%;
height: 100%;
border: none;
background: transparent;
outline: none;
font-size: 14px;
color: var(--text-color, #333);
}
.action-button {
flex-shrink: 0;
height: 36px;
padding: 0 16px;
border: none;
border-radius: 18px;
background: transparent;
color: var(--text-color, #333);
font-size: 14px;
cursor: pointer;
transition: all 0.2s;
}
.action-button:hover {
background: var(--button-hover-bg, #f1f3f4);
}
/* 修改CSS变量 */
:root {
--tab-bar-bg: #f1f3f4;
--tab-bg: rgba(32, 33, 36, 0.1);
--tab-hover-bg: rgba(32, 33, 36, 0.08);
--tab-active-bg: #fff;
--tab-separator-color: rgba(0, 0, 0, 0.2);
--text-color: #5f6368;
--active-text-color: #202124;
--icon-color: #5f6368;
--close-hover-bg: rgba(0, 0, 0, 0.08);
--primary-color: #1a73e8;
--new-tab-shadow: 0 -8px 12px -6px rgba(0, 0, 0, 0.1);
}
[data-theme="dark"] {
--tab-bar-bg: #202124;
--tab-bg: rgba(255, 255, 255, 0.1);
--tab-hover-bg: rgba(255, 255, 255, 0.08);
--tab-active-bg: #292a2d;
--tab-separator-color: rgba(255, 255, 255, 0.2);
--text-color: #9ba0a5;
--active-text-color: #e8eaed;
--icon-color: #9ba0a5;
--close-hover-bg: rgba(255, 255, 255, 0.08);
--primary-color: #8ab4f8;
--new-tab-shadow: 0 -8px 12px -6px rgba(0, 0, 0, 0.3);
}
</style>

Loading…
Cancel
Save