|
|
@ -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> |
|
|
|