3 changed files with 468 additions and 465 deletions
@ -0,0 +1,462 @@ |
|||
<script setup lang="ts"> |
|||
import { onBeforeMount, onBeforeUnmount, onMounted, ref, useTemplateRef, nextTick } from "vue" |
|||
import { PopupMenu } from "@/bridge/PopupMenu" |
|||
|
|||
// const PlaceHolderRef = useTemplateRef("PlaceHolder") |
|||
// function OnResize() { |
|||
// const el = PlaceHolderRef.value |
|||
// if (el) { |
|||
// const rect = el.getBoundingClientRect().toJSON() |
|||
// console.log(rect) |
|||
// api.call("TabsCommand.bindElement", rect) |
|||
// } |
|||
// } |
|||
// onMounted(OnResize) |
|||
// window.addEventListener("resize", OnResize) |
|||
// onBeforeUnmount(() => { |
|||
// window.removeEventListener("resize", OnResize) |
|||
// }) |
|||
|
|||
const PlaceHolder = useTemplateRef("PlaceHolder") |
|||
const { stop } = useResizeObserver(PlaceHolder, () => { |
|||
const el = PlaceHolder.value |
|||
if (el) { |
|||
const rect = el.getBoundingClientRect().toJSON() |
|||
api.call("TabsCommand.bindElement", rect) |
|||
} |
|||
}) |
|||
|
|||
onBeforeUnmount(() => { |
|||
stop() |
|||
}) |
|||
|
|||
const list = ref<any[]>([]) |
|||
const curUrl = ref<any>("") |
|||
const curIndex = ref<any>(-1) |
|||
const listener = (_, v) => { |
|||
list.value = v |
|||
const el = v.find(v => v.isActive) |
|||
curIndex.value = v.findIndex(v => v.isActive) |
|||
if (el) { |
|||
curUrl.value = el.showUrl |
|||
} else { |
|||
curUrl.value = "" |
|||
} |
|||
} |
|||
if (import.meta.hot) { |
|||
api.off("TabsCommand.update", listener) |
|||
} |
|||
api.on("TabsCommand.update", listener) |
|||
onMounted(() => { |
|||
api.call("TabsCommand.sync") |
|||
}) |
|||
|
|||
onBeforeMount(async () => { |
|||
list.value = await fetch("api://fuck/TabsService/getAllTabs").then(async res => await res.json()) |
|||
}) |
|||
|
|||
// const url = ref("") |
|||
|
|||
// async function addTab() { |
|||
// if (!url.value) url.value = "about:blank" |
|||
// await fetch("api://fuck/TabsService/add", { |
|||
// method: "POST", |
|||
// body: JSON.stringify({ url: url.value }), |
|||
// }) |
|||
// url.value = "" |
|||
// onClick() |
|||
// } |
|||
|
|||
function handleTabContextMenu(_, index) { |
|||
const menu = new PopupMenu([ |
|||
{ |
|||
label: "右侧关闭", |
|||
click() { |
|||
const all: number[] = [] |
|||
list.value.forEach((_, i) => { |
|||
if (i <= index) return |
|||
all.push(i) |
|||
}) |
|||
fetch("api://fuck/TabsService/closeTabAll", { |
|||
method: "POST", |
|||
body: JSON.stringify({ active: all }), |
|||
}) |
|||
}, |
|||
}, |
|||
{ |
|||
type: "separator", |
|||
}, |
|||
]) |
|||
menu.show() |
|||
} |
|||
|
|||
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() { |
|||
if (curUrl.value) { |
|||
if (curIndex.value !== undefined && curIndex.value >= 0) { |
|||
api.call("TabsCommand.nagivate", curIndex.value, curUrl.value) |
|||
} else { |
|||
api.call("TabsCommand.add", curUrl.value) |
|||
} |
|||
} |
|||
} |
|||
|
|||
async function addTab() { |
|||
await api.call("TabsCommand.add", "about:blank") |
|||
scrollTabIntoView(list.value.length - 1) |
|||
} |
|||
|
|||
async function closeTab(_, index) { |
|||
await fetch("api://fuck/TabsService/closeTab", { |
|||
method: "POST", |
|||
body: JSON.stringify({ active: index }), |
|||
}) |
|||
onClick() |
|||
} |
|||
|
|||
const onClick = async () => { |
|||
list.value = await api.call("TabsCommand.getAllTabs") |
|||
// list.value = await fetch("api://fuck/TabsService/getAllTabs").then(async res => await res.json()) |
|||
// fetch("api://fuck/BasicService/showAbout").then(async res => console.log(await res.json())) |
|||
// fetch("api://index/openAbout", { |
|||
// method: "POST", |
|||
// body: JSON.stringify({ a: "234" }), |
|||
// }).then(async res => console.log(await res.json())) |
|||
} |
|||
|
|||
function onClickDevTool() { |
|||
fetch("api://fuck/BasicService/openTabDevtool") |
|||
} |
|||
</script> |
|||
|
|||
<template> |
|||
<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 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 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> |
|||
<!-- 保持原有内容 --> |
|||
</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> |
@ -1,478 +1,18 @@ |
|||
<script setup lang="ts"> |
|||
import { onBeforeMount, onBeforeUnmount, onMounted, ref, useTemplateRef, nextTick } from "vue" |
|||
import { PopupMenu } from "@/bridge/PopupMenu" |
|||
|
|||
// const PlaceHolderRef = useTemplateRef("PlaceHolder") |
|||
// function OnResize() { |
|||
// const el = PlaceHolderRef.value |
|||
// if (el) { |
|||
// const rect = el.getBoundingClientRect().toJSON() |
|||
// console.log(rect) |
|||
// api.call("TabsCommand.bindElement", rect) |
|||
// } |
|||
// } |
|||
// onMounted(OnResize) |
|||
// window.addEventListener("resize", OnResize) |
|||
// onBeforeUnmount(() => { |
|||
// window.removeEventListener("resize", OnResize) |
|||
// }) |
|||
|
|||
const PlaceHolder = useTemplateRef("PlaceHolder") |
|||
const { stop } = useResizeObserver(PlaceHolder, () => { |
|||
const el = PlaceHolder.value |
|||
if (el) { |
|||
const rect = el.getBoundingClientRect().toJSON() |
|||
api.call("TabsCommand.bindElement", rect) |
|||
} |
|||
}) |
|||
|
|||
onBeforeUnmount(() => { |
|||
stop() |
|||
}) |
|||
|
|||
const list = ref<any[]>([]) |
|||
const curUrl = ref<any>("") |
|||
const curIndex = ref<any>(-1) |
|||
const listener = (_, v) => { |
|||
list.value = v |
|||
const el = v.find(v => v.isActive) |
|||
curIndex.value = v.findIndex(v => v.isActive) |
|||
if (el) { |
|||
curUrl.value = el.showUrl |
|||
} else { |
|||
curUrl.value = "" |
|||
} |
|||
} |
|||
if (import.meta.hot) { |
|||
api.off("TabsCommand.update", listener) |
|||
} |
|||
api.on("TabsCommand.update", listener) |
|||
onMounted(() => { |
|||
api.call("TabsCommand.sync") |
|||
}) |
|||
|
|||
onBeforeMount(async () => { |
|||
list.value = await fetch("api://fuck/TabsService/getAllTabs").then(async res => await res.json()) |
|||
}) |
|||
|
|||
// const url = ref("") |
|||
|
|||
// async function addTab() { |
|||
// if (!url.value) url.value = "about:blank" |
|||
// await fetch("api://fuck/TabsService/add", { |
|||
// method: "POST", |
|||
// body: JSON.stringify({ url: url.value }), |
|||
// }) |
|||
// url.value = "" |
|||
// onClick() |
|||
// } |
|||
|
|||
function handleTabContextMenu(_, index) { |
|||
const menu = new PopupMenu([ |
|||
{ |
|||
label: "右侧关闭", |
|||
click() { |
|||
const all: number[] = [] |
|||
list.value.forEach((_, i) => { |
|||
if (i <= index) return |
|||
all.push(i) |
|||
}) |
|||
fetch("api://fuck/TabsService/closeTabAll", { |
|||
method: "POST", |
|||
body: JSON.stringify({ active: all }), |
|||
}) |
|||
}, |
|||
}, |
|||
{ |
|||
type: "separator", |
|||
}, |
|||
]) |
|||
menu.show() |
|||
} |
|||
|
|||
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() { |
|||
if (curUrl.value) { |
|||
if (curIndex.value !== undefined && curIndex.value >= 0) { |
|||
api.call("TabsCommand.nagivate", curIndex.value, curUrl.value) |
|||
} else { |
|||
api.call("TabsCommand.add", curUrl.value) |
|||
} |
|||
} |
|||
} |
|||
|
|||
async function addTab() { |
|||
await api.call("TabsCommand.add", "about:blank") |
|||
scrollTabIntoView(list.value.length - 1) |
|||
} |
|||
|
|||
async function closeTab(_, index) { |
|||
await fetch("api://fuck/TabsService/closeTab", { |
|||
method: "POST", |
|||
body: JSON.stringify({ active: index }), |
|||
}) |
|||
onClick() |
|||
} |
|||
|
|||
const onClick = async () => { |
|||
list.value = await api.call("TabsCommand.getAllTabs") |
|||
// list.value = await fetch("api://fuck/TabsService/getAllTabs").then(async res => await res.json()) |
|||
// fetch("api://fuck/BasicService/showAbout").then(async res => console.log(await res.json())) |
|||
// fetch("api://index/openAbout", { |
|||
// method: "POST", |
|||
// body: JSON.stringify({ a: "234" }), |
|||
// }).then(async res => console.log(await res.json())) |
|||
} |
|||
|
|||
function onClickDevTool() { |
|||
fetch("api://fuck/BasicService/openTabDevtool") |
|||
} |
|||
import Browser from "./_ui/Browser.vue" |
|||
</script> |
|||
|
|||
<template> |
|||
<div h-full flex> |
|||
<div w="200px" relative> |
|||
<div p="8px 10px" text="12px" @click="$router.push('/_ui/Browser')" border border-b cursor="pointer" hover:bg-gray-100>浏览器</div> |
|||
<div p="8px 10px" text="12px" border border-b cursor="pointer" hover:bg-gray-100>浏览器</div> |
|||
<AdjustLine></AdjustLine> |
|||
</div> |
|||
<div b-l="1px solid #E5E5E5" flex-1 w-0 overflow-auto flex flex-col> |
|||
<!-- 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 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 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> |
|||
<!-- 保持原有内容 --> |
|||
</div> |
|||
<Browser></Browser> |
|||
</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> |
|||
<style lang="scss" scoped></style> |
|||
|
Loading…
Reference in new issue