优化桌面端标签导航与后台交互
Client Quality Gates / Shared client and Web (push) Has been cancelled
Client Quality Gates / macOS Desktop (push) Has been cancelled

This commit is contained in:
Cheng Zhou
2026-07-09 08:56:13 +08:00
parent 76f2d9f22a
commit 0aff10fe0d
17 changed files with 1232 additions and 207 deletions
+498 -42
View File
@@ -5,7 +5,7 @@
<div class="sidebar-title-row">
<h1>{{ TEXT.common.appName }}</h1>
<button v-if="!study.currentStudy" class="study-switcher-trigger empty" type="button" @click="openDesktopProjectEntry">
<button v-if="showSidebarProjectChooser" class="study-switcher-trigger empty" type="button" @click="openDesktopProjectEntry">
选择项目
</button>
</div>
@@ -269,26 +269,87 @@
</div>
</header>
<div class="workspace-tabs" v-if="workspaceTabs.length">
<div v-if="workspaceTabs.length" ref="workspaceTabsElement" class="workspace-tabs">
<div
v-for="item in workspaceTabs"
:key="item.path"
class="workspace-tab"
:class="{ active: activeMenu === item.path, dragging: draggingWorkspaceTabPath === item.path }"
draggable="true"
@dragstart="startWorkspaceTabDrag(item.path, $event)"
@dragover="allowWorkspaceTabDrop"
@drop.prevent="dropWorkspaceTab(item.path, $event)"
@dragend="draggingWorkspaceTabPath = ''"
@contextmenu.prevent.stop="openWorkspaceTabMenu(item, $event)"
class="workspace-tabs-list"
:style="{ gridTemplateColumns: `repeat(${visibleWorkspaceTabs.length}, minmax(0, 238px))` }"
>
<button class="workspace-tab-action" type="button" @click="navigateWorkspaceTab(item.path)">
<span>{{ item.title }}</span>
</button>
<button class="tab-close" type="button" title="关闭标签" @click.stop="closeWorkspaceTab(item.path)">
×
</button>
<div
v-for="item in visibleWorkspaceTabs"
:key="item.path"
class="workspace-tab"
:class="{ active: activeMenu === item.path, dragging: draggingWorkspaceTabPath === item.path }"
draggable="true"
@dragstart="startWorkspaceTabDrag(item.path, $event)"
@dragover="allowWorkspaceTabDrop"
@drop.prevent="dropWorkspaceTab(item.path, $event)"
@dragend="draggingWorkspaceTabPath = ''"
@contextmenu.prevent.stop="openWorkspaceTabMenu(item, $event)"
>
<button
class="workspace-tab-action"
type="button"
:title="item.title"
@click="navigateWorkspaceTab(item.path)"
>
<span>{{ item.title }}</span>
</button>
<button class="tab-close" type="button" title="关闭标签" @click.stop="closeWorkspaceTab(item.path)">
×
</button>
</div>
</div>
<el-dropdown
v-if="overflowWorkspaceTabs.length"
class="workspace-tabs-overflow"
popper-class="workspace-tabs-overflow-popper"
trigger="click"
placement="bottom-end"
@command="navigateOverflowWorkspaceTab"
@visible-change="workspaceTabsOverflowOpen = $event"
>
<button
class="workspace-tabs-overflow-trigger"
:class="{ 'is-open': workspaceTabsOverflowOpen }"
type="button"
:aria-label="`查看其余 ${overflowWorkspaceTabs.length} 个标签`"
>
<span>更多</span>
<span class="workspace-tabs-overflow-count">{{ overflowWorkspaceTabs.length }}</span>
<el-icon><ArrowDown /></el-icon>
</button>
<template #dropdown>
<div class="workspace-tabs-overflow-menu">
<div class="workspace-tabs-overflow-header">
<span>其他标签</span>
<span>{{ overflowWorkspaceTabs.length }} </span>
</div>
<el-dropdown-menu>
<el-dropdown-item
v-for="item in overflowWorkspaceTabs"
:key="item.path"
:command="item.path"
:class="{ 'is-current': activeMenu === item.path }"
>
<span class="workspace-tabs-overflow-item-mark" aria-hidden="true"></span>
<span class="workspace-tabs-overflow-label">
<span :title="item.title">{{ item.title }}</span>
</span>
<button
class="workspace-tabs-overflow-close"
type="button"
:aria-label="`关闭标签:${item.title}`"
title="关闭标签"
@click.stop="closeWorkspaceTab(item.path)"
>
×
</button>
</el-dropdown-item>
</el-dropdown-menu>
</div>
</template>
</el-dropdown>
</div>
<main class="desktop-content">
@@ -415,13 +476,17 @@ import {
} from "../session/desktopActivityCenter";
import {
DESKTOP_SERVER_URL_CHANGED_EVENT,
DESKTOP_SHORTCUTS_CHANGED_EVENT,
clearLegacyDesktopRouteHistoryPreference,
formatDesktopShortcut,
getAppMetadata,
getDesktopServerUrl,
listenDesktopMenuCommand,
readDesktopFavoriteRoutes,
readDesktopShortcutPreferences,
toggleDesktopFavoriteRoute,
type DesktopRoutePreference,
type DesktopShortcutPreferences,
} from "../runtime";
import { dispatchDesktopRefreshCurrentView } from "../composables/useDesktopRefresh";
import { useDesktopShortcuts } from "../composables/useDesktopShortcuts";
@@ -438,6 +503,14 @@ import {
type LayoutNavigationIcon,
type LayoutNavigationItem,
} from "./layout/navigation";
import {
createWorkspaceTabHistory,
currentWorkspaceTabPath,
moveWorkspaceTabHistory,
recordWorkspaceTabPath,
replaceWorkspaceTabPath,
type WorkspaceTabHistory,
} from "./layout/workspaceTabHistory";
const auth = useAuthStore();
const study = useStudyStore();
@@ -445,6 +518,10 @@ const router = useRouter();
const route = useRoute();
const desktopMetadata = getAppMetadata();
const DESKTOP_WORKSPACE_TAB_CACHE_MAX = 12;
const DESKTOP_WORKSPACE_TAB_MIN_WIDTH = 130;
const DESKTOP_WORKSPACE_TAB_GAP = 6;
const DESKTOP_WORKSPACE_TABS_HORIZONTAL_PADDING = 20;
const DESKTOP_WORKSPACE_TABS_OVERFLOW_WIDTH = 106;
const DESKTOP_ADMIN_SECTION_LABEL = "系统管理";
const isAdmin = computed(() => !!auth.user?.is_admin);
const projectRole = computed(() => study.currentStudyRole || (study.currentStudy as any)?.role_in_study || "");
@@ -453,6 +530,7 @@ const canAccessProjectPath = (path: string) =>
hasProjectPermission(study.currentPermissions, projectRole.value, getProjectRoutePermission(path), isAdmin.value);
const hasAnyProjectModuleAccess = computed(() => projectRouteLandingPaths.some((path) => canAccessProjectPath(path)));
const isAdminContext = computed(() => route.path.startsWith("/admin"));
const showSidebarProjectChooser = computed(() => !study.currentStudy && !isAdminContext.value);
const loggingOut = ref(false);
const commandPaletteVisible = ref(false);
const desktopPreferencesVisible = ref(false);
@@ -462,9 +540,15 @@ const desktopServerUrl = ref(getDesktopServerUrl());
const desktopUpdateStatus = ref<DesktopUpdateStatusSnapshot>(getDesktopUpdateStatus());
const desktopActivities = ref<DesktopActivityItem[]>(getDesktopActivities());
const desktopFavoriteRoutes = ref<DesktopRoutePreference[]>(readDesktopFavoriteRoutes());
const desktopShortcutPreferences = ref<DesktopShortcutPreferences>(readDesktopShortcutPreferences());
const workspaceTabs = ref<DesktopRoutePreference[]>([]);
const workspaceTabHistories = ref<Record<string, WorkspaceTabHistory>>({});
const workspaceTabsElement = ref<HTMLElement | null>(null);
const workspaceTabsAvailableWidth = ref(0);
const workspaceTabsOverflowOpen = ref(false);
const draggingWorkspaceTabPath = ref("");
const lastClosedWorkspaceTab = ref<DesktopRoutePreference | null>(null);
const lastClosedWorkspaceTabHistory = ref<WorkspaceTabHistory | null>(null);
const workspaceTabMenu = ref<{
visible: boolean;
x: number;
@@ -477,11 +561,13 @@ const workspaceTabMenu = ref<{
tab: null,
});
const expandedNavigationGroups = ref<Set<string>>(new Set());
const collapsedNavigationGroups = ref<Set<string>>(new Set());
const headerRemindersLoading = ref(false);
const headerReminderStats = ref({ overdueAes: 0, overdueMonitoringIssues: 0 });
let desktopMenuUnlisten: (() => void) | undefined;
let desktopUpdateStatusUnlisten: (() => void) | undefined;
let desktopActivityUnlisten: (() => void) | undefined;
let workspaceTabsResizeObserver: ResizeObserver | undefined;
const iconComponentMap = {
audit: Document,
@@ -507,17 +593,23 @@ const activeMenu = computed(() => getActiveLayoutPath(route.path));
const navigationGroupKey = (item: LayoutNavigationItem) => `${item.group}:${item.label}:${item.path}`;
const isNavigationGroupActive = (item: LayoutNavigationItem) =>
Boolean(item.children?.some((child) => activeMenu.value === child.path));
const isNavigationGroupExpanded = (item: LayoutNavigationItem) =>
isNavigationGroupActive(item) || expandedNavigationGroups.value.has(navigationGroupKey(item));
const isNavigationGroupExpanded = (item: LayoutNavigationItem) => {
const key = navigationGroupKey(item);
return expandedNavigationGroups.value.has(key) || (isNavigationGroupActive(item) && !collapsedNavigationGroups.value.has(key));
};
const toggleNavigationGroup = (item: LayoutNavigationItem) => {
const key = navigationGroupKey(item);
const next = new Set(expandedNavigationGroups.value);
if (next.has(key)) {
next.delete(key);
const nextExpanded = new Set(expandedNavigationGroups.value);
const nextCollapsed = new Set(collapsedNavigationGroups.value);
if (isNavigationGroupExpanded(item)) {
nextExpanded.delete(key);
nextCollapsed.add(key);
} else {
next.add(key);
nextExpanded.add(key);
nextCollapsed.delete(key);
}
expandedNavigationGroups.value = next;
expandedNavigationGroups.value = nextExpanded;
collapsedNavigationGroups.value = nextCollapsed;
};
const adminNavigationItems = computed(() =>
study.currentStudy
@@ -540,6 +632,10 @@ const projectDesktopNavigationItems = computed(() => flattenLayoutNavigationItem
const desktopNavigationItems = computed(() =>
[...adminDesktopNavigationItems.value, ...projectDesktopNavigationItems.value],
);
const workspaceTabFallbackPath = computed(() => {
if (isAdminContext.value) return adminDesktopNavigationItems.value[0]?.path || "/admin/projects";
return study.currentStudy ? "/project/overview" : "/desktop/project-entry";
});
const userDisplayName = computed(
() => auth.user?.full_name || auth.user?.username || auth.user?.email || TEXT.common.labels.userFallback,
@@ -595,6 +691,35 @@ const currentDesktopRoutePreference = computed(() => {
if (!item) return null;
return { path: item.path, title: item.label, group: item.group };
});
const visibleWorkspaceTabCapacity = computed(() => {
const tabCount = workspaceTabs.value.length;
const availableWidth = workspaceTabsAvailableWidth.value;
if (!tabCount || availableWidth <= 0) return tabCount;
const maxWithoutOverflow = Math.max(
1,
Math.floor((availableWidth + DESKTOP_WORKSPACE_TAB_GAP) / (DESKTOP_WORKSPACE_TAB_MIN_WIDTH + DESKTOP_WORKSPACE_TAB_GAP)),
);
if (tabCount <= maxWithoutOverflow) return tabCount;
return Math.max(
1,
Math.floor(
(availableWidth - DESKTOP_WORKSPACE_TABS_OVERFLOW_WIDTH) /
(DESKTOP_WORKSPACE_TAB_MIN_WIDTH + DESKTOP_WORKSPACE_TAB_GAP),
),
);
});
const visibleWorkspaceTabs = computed(() => {
const capacity = visibleWorkspaceTabCapacity.value;
if (workspaceTabs.value.length <= capacity) return workspaceTabs.value;
const firstTabs = workspaceTabs.value.slice(0, capacity);
const activeTab = workspaceTabs.value.find((item) => item.path === activeMenu.value);
if (!activeTab || firstTabs.some((item) => item.path === activeTab.path)) return firstTabs;
return [...firstTabs.slice(0, -1), activeTab];
});
const overflowWorkspaceTabs = computed(() => {
const visiblePaths = new Set(visibleWorkspaceTabs.value.map((item) => item.path));
return workspaceTabs.value.filter((item) => !visiblePaths.has(item.path));
});
const currentRouteFavorited = computed(() => {
const current = currentDesktopRoutePreference.value;
return Boolean(current && desktopFavoriteRoutes.value.some((item) => item.path === current.path));
@@ -673,11 +798,64 @@ const addWorkspaceTab = (routePreference: Pick<DesktopRoutePreference, "path" |
workspaceTabs.value = [...workspaceTabs.value, item];
};
const setWorkspaceTabHistory = (tabPath: string, history: WorkspaceTabHistory) => {
workspaceTabHistories.value = { ...workspaceTabHistories.value, [tabPath]: history };
};
const removeWorkspaceTabHistory = (tabPath: string) => {
if (!workspaceTabHistories.value[tabPath]) return;
const next = { ...workspaceTabHistories.value };
delete next[tabPath];
workspaceTabHistories.value = next;
};
const recordWorkspaceTabRoute = (tabPath: string, fullPath: string) => {
setWorkspaceTabHistory(tabPath, recordWorkspaceTabPath(workspaceTabHistories.value[tabPath], fullPath));
};
const replaceWorkspaceTabRoute = (tabPath: string, fullPath: string) => {
setWorkspaceTabHistory(tabPath, replaceWorkspaceTabPath(workspaceTabHistories.value[tabPath], fullPath));
};
const workspaceTabDestination = (tabPath: string) =>
currentWorkspaceTabPath(workspaceTabHistories.value[tabPath], tabPath);
const desktopRouteCacheKey = (currentRoute: { fullPath: string }) => currentRoute.fullPath;
const navigateWorkspaceTab = (path: string) => {
if (activeMenu.value === path) return;
void router.push(path);
void router.push(workspaceTabDestination(path));
};
const navigateCurrentWorkspaceTabHistory = (offset: -1 | 1) => {
const tabPath = activeMenu.value;
const movement = moveWorkspaceTabHistory(workspaceTabHistories.value[tabPath], offset);
if (!movement) return;
setWorkspaceTabHistory(tabPath, movement.history);
void router.push(movement.path);
};
const navigateOverflowWorkspaceTab = (path: string) => {
navigateWorkspaceTab(path);
};
const updateWorkspaceTabsAvailableWidth = () => {
const element = workspaceTabsElement.value;
workspaceTabsAvailableWidth.value = element
? Math.max(0, element.clientWidth - DESKTOP_WORKSPACE_TABS_HORIZONTAL_PADDING)
: 0;
};
const observeWorkspaceTabsElement = (element: HTMLElement | null) => {
workspaceTabsResizeObserver?.disconnect();
if (!element) {
workspaceTabsAvailableWidth.value = 0;
return;
}
updateWorkspaceTabsAvailableWidth();
if (typeof ResizeObserver === "undefined") return;
workspaceTabsResizeObserver ||= new ResizeObserver(updateWorkspaceTabsAvailableWidth);
workspaceTabsResizeObserver.observe(element);
};
const closeWorkspaceTabMenu = () => {
@@ -734,17 +912,21 @@ const dropWorkspaceTab = (targetPath: string, event: DragEvent) => {
const closeWorkspaceTab = (path: string) => {
const index = workspaceTabs.value.findIndex((tab) => tab.path === path);
if (index >= 0) lastClosedWorkspaceTab.value = workspaceTabs.value[index];
if (index >= 0) {
lastClosedWorkspaceTab.value = workspaceTabs.value[index];
lastClosedWorkspaceTabHistory.value = workspaceTabHistories.value[path] || createWorkspaceTabHistory(path);
}
workspaceTabs.value = workspaceTabs.value.filter((tab) => tab.path !== path);
removeWorkspaceTabHistory(path);
if (activeMenu.value !== path) return;
const next =
workspaceTabs.value[index] ||
workspaceTabs.value[index - 1] ||
desktopFavoriteRoutes.value.find((item) => item.path !== path);
if (next) {
router.push(next.path);
router.push(workspaceTabDestination(next.path));
} else {
router.push(study.currentStudy ? "/project/overview" : "/desktop/project-entry");
router.push(workspaceTabFallbackPath.value);
}
};
@@ -757,7 +939,9 @@ const closeOtherWorkspaceTabs = (path: string) => {
const target = workspaceTabs.value.find((tab) => tab.path === path);
if (!target) return;
workspaceTabs.value = [target];
if (activeMenu.value !== path) void router.push(path);
const targetHistory = workspaceTabHistories.value[path];
workspaceTabHistories.value = targetHistory ? { [path]: targetHistory } : {};
if (activeMenu.value !== path) void router.push(workspaceTabDestination(path));
closeWorkspaceTabMenu();
};
@@ -765,8 +949,12 @@ const restoreLastClosedWorkspaceTab = () => {
const tab = lastClosedWorkspaceTab.value;
if (!tab) return;
addWorkspaceTab(tab);
void router.push(tab.path);
if (lastClosedWorkspaceTabHistory.value) {
setWorkspaceTabHistory(tab.path, lastClosedWorkspaceTabHistory.value);
}
void router.push(workspaceTabDestination(tab.path));
lastClosedWorkspaceTab.value = null;
lastClosedWorkspaceTabHistory.value = null;
closeWorkspaceTabMenu();
};
@@ -774,6 +962,11 @@ const updateDesktopServerUrl = () => {
desktopServerUrl.value = getDesktopServerUrl();
};
const updateDesktopShortcutPreferences = (event?: Event) => {
desktopShortcutPreferences.value =
(event as CustomEvent<DesktopShortcutPreferences> | undefined)?.detail || readDesktopShortcutPreferences();
};
const openCommandPalette = () => {
commandPaletteVisible.value = true;
};
@@ -785,7 +978,9 @@ const openDesktopPreferences = () => {
const openDesktopProjectEntry = async () => {
study.clearCurrentStudy();
workspaceTabs.value = [];
workspaceTabHistories.value = {};
lastClosedWorkspaceTab.value = null;
lastClosedWorkspaceTabHistory.value = null;
await router.push("/desktop/project-entry");
};
@@ -819,11 +1014,11 @@ const handleDesktopMenuCommand = (command: string) => {
return;
}
if (command === "ctms.desktop.back") {
router.back();
navigateCurrentWorkspaceTabHistory(-1);
return;
}
if (command === "ctms.desktop.forward") {
router.forward();
navigateCurrentWorkspaceTabHistory(1);
}
};
@@ -847,11 +1042,27 @@ const desktopCommands = computed<DesktopCommand[]>(() => {
run: () => navigateWorkspaceTab(item.path),
}));
return [
{
id: "desktop:tab-back",
title: "当前标签后退",
group: "标签页",
shortcut: formatDesktopShortcut(desktopShortcutPreferences.value.back),
visible: true,
run: () => navigateCurrentWorkspaceTabHistory(-1),
},
{
id: "desktop:tab-forward",
title: "当前标签前进",
group: "标签页",
shortcut: formatDesktopShortcut(desktopShortcutPreferences.value.forward),
visible: true,
run: () => navigateCurrentWorkspaceTabHistory(1),
},
{
id: "desktop:refresh",
title: "刷新当前视图",
group: "桌面",
shortcut: "⌘R",
shortcut: formatDesktopShortcut(desktopShortcutPreferences.value.refresh),
visible: true,
run: refreshCurrentDesktopView,
},
@@ -1009,12 +1220,20 @@ const beforeProfileDialogClose = async (done: () => void) => {
};
watch(
() => route.path,
() => {
() => route.fullPath,
(fullPath, previousFullPath) => {
study.setViewContext(null);
const current = currentDesktopRoutePreference.value;
if (current) {
addWorkspaceTab(current);
const previousTabPath = previousFullPath ? getActiveLayoutPath(router.resolve(previousFullPath).path) : "";
const replacesCurrentTabEntry =
previousTabPath === current.path && Boolean((window.history.state as { replaced?: boolean } | null)?.replaced);
if (replacesCurrentTabEntry) {
replaceWorkspaceTabRoute(current.path, fullPath);
} else {
recordWorkspaceTabRoute(current.path, fullPath);
}
refreshDesktopRoutePreferences();
}
},
@@ -1033,12 +1252,17 @@ watch(
() => refreshDesktopRoutePreferences(),
);
watch(workspaceTabsElement, (element) => observeWorkspaceTabsElement(element), { flush: "post" });
onMounted(async () => {
window.addEventListener(DESKTOP_SERVER_URL_CHANGED_EVENT, updateDesktopServerUrl);
window.addEventListener(DESKTOP_SHORTCUTS_CHANGED_EVENT, updateDesktopShortcutPreferences);
window.addEventListener("resize", updateWorkspaceTabsAvailableWidth);
clearLegacyDesktopRouteHistoryPreference();
const current = currentDesktopRoutePreference.value;
if (current) {
addWorkspaceTab(current);
recordWorkspaceTabRoute(current.path, route.fullPath);
}
refreshDesktopRoutePreferences();
desktopMenuUnlisten = await listenDesktopMenuCommand(handleDesktopMenuCommand).catch(() => undefined);
@@ -1065,6 +1289,9 @@ onMounted(async () => {
onBeforeUnmount(() => {
window.removeEventListener(DESKTOP_SERVER_URL_CHANGED_EVENT, updateDesktopServerUrl);
window.removeEventListener(DESKTOP_SHORTCUTS_CHANGED_EVENT, updateDesktopShortcutPreferences);
window.removeEventListener("resize", updateWorkspaceTabsAvailableWidth);
workspaceTabsResizeObserver?.disconnect();
desktopMenuUnlisten?.();
desktopUpdateStatusUnlisten?.();
desktopActivityUnlisten?.();
@@ -1089,8 +1316,8 @@ useDesktopShortcuts(
}
return false;
},
navigateBack: () => router.back(),
navigateForward: () => router.forward(),
navigateBack: () => navigateCurrentWorkspaceTabHistory(-1),
navigateForward: () => navigateCurrentWorkspaceTabHistory(1),
refreshCurrentView: refreshCurrentDesktopView,
},
);
@@ -1772,22 +1999,31 @@ useDesktopShortcuts(
.workspace-tabs {
display: flex;
align-items: center;
min-width: 0;
gap: 6px;
padding: 7px 10px;
overflow-x: auto;
overflow: hidden;
border-bottom: 1px solid #dfe7f0;
background: #f4f7fa;
}
.workspace-tabs-list {
display: grid;
flex: 1 1 auto;
min-width: 0;
gap: 6px;
overflow: hidden;
}
.workspace-tab {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
align-items: center;
gap: 8px;
min-width: 150px;
max-width: 238px;
min-width: 0;
height: 30px;
box-sizing: border-box;
padding: 0 5px 0 10px;
border: 1px solid #d2dce8;
border-radius: 8px;
@@ -1848,6 +2084,172 @@ useDesktopShortcuts(
color: #142033;
}
.workspace-tabs-overflow {
display: flex;
flex: 0 0 106px;
min-width: 0;
}
.workspace-tabs-overflow-trigger {
appearance: none;
display: flex;
align-items: center;
justify-content: center;
width: 106px;
height: 30px;
gap: 6px;
padding: 0 9px 0 11px;
border: 1px solid #c5d1df;
border-radius: 8px;
background: #ffffff;
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
color: #3d5068;
cursor: pointer;
font: inherit;
font-size: 12px;
font-weight: 700;
white-space: nowrap;
}
.workspace-tabs-overflow-trigger:hover,
.workspace-tabs-overflow-trigger:focus-visible,
.workspace-tabs-overflow-trigger.is-open {
border-color: #aebfd1;
background: #eaf1f8;
color: #183756;
outline: none;
}
.workspace-tabs-overflow-count {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 20px;
height: 18px;
box-sizing: border-box;
padding: 0 6px;
border-radius: 9px;
background: #e3ebf4;
color: #31506f;
font-size: 10px;
line-height: 1;
}
:global(.workspace-tabs-overflow-popper.el-popper) {
overflow: hidden;
padding: 0;
border: 1px solid #d6e0eb;
border-radius: 10px;
background: #ffffff;
box-shadow: 0 14px 38px rgba(15, 23, 42, 0.16), 0 2px 8px rgba(15, 23, 42, 0.08);
}
:global(.workspace-tabs-overflow-popper .el-popper__arrow::before) {
border-color: #d6e0eb;
background: #ffffff;
}
.workspace-tabs-overflow-menu {
width: 252px;
}
.workspace-tabs-overflow-header {
display: flex;
align-items: center;
justify-content: space-between;
height: 38px;
padding: 0 12px;
border-bottom: 1px solid #e5ebf2;
color: #2d4058;
font-size: 12px;
font-weight: 800;
}
.workspace-tabs-overflow-header span:last-child {
color: #8292a6;
font-size: 11px;
font-weight: 700;
}
:global(.workspace-tabs-overflow-popper .el-dropdown-menu) {
min-width: 0;
max-height: min(360px, calc(100vh - 120px));
padding: 6px;
border: 0;
background: transparent;
overflow-y: auto;
}
:global(.workspace-tabs-overflow-popper .el-dropdown-menu__item) {
display: flex;
min-height: 38px;
gap: 9px;
padding: 0 6px 0 10px;
border-radius: 7px;
color: #33485f;
font-size: 12px;
font-weight: 700;
}
:global(.workspace-tabs-overflow-popper .el-dropdown-menu__item:hover),
:global(.workspace-tabs-overflow-popper .el-dropdown-menu__item:focus) {
background: #f0f5fa;
color: #183756;
}
:global(.workspace-tabs-overflow-popper .el-dropdown-menu__item.is-current) {
background: #eaf1f8;
color: #183756;
}
.workspace-tabs-overflow-label {
display: block;
flex: 1 1 auto;
min-width: 0;
}
.workspace-tabs-overflow-item-mark {
flex: 0 0 7px;
width: 7px;
height: 7px;
border-radius: 50%;
background: #9fb1c4;
}
.workspace-tabs-overflow-label > span {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.workspace-tabs-overflow-close {
appearance: none;
display: inline-flex;
align-items: center;
justify-content: center;
flex: 0 0 24px;
width: 24px;
height: 24px;
padding: 0;
border: 0;
border-radius: 6px;
background: transparent;
color: #7b8da3;
cursor: pointer;
font: inherit;
font-size: 16px;
opacity: 0.62;
}
.workspace-tabs-overflow-close:hover,
.workspace-tabs-overflow-close:focus-visible {
background: rgba(15, 23, 42, 0.08);
color: #142033;
opacity: 1;
outline: none;
}
.workspace-tab-context-menu {
position: fixed;
z-index: 2300;
@@ -2007,7 +2409,8 @@ useDesktopShortcuts(
:global([data-ctms-theme="dark"] .update-notice),
:global([data-ctms-theme="dark"] .connection-button),
:global([data-ctms-theme="dark"] .account-button),
:global([data-ctms-theme="dark"] .workspace-tab) {
:global([data-ctms-theme="dark"] .workspace-tab),
:global([data-ctms-theme="dark"] .workspace-tabs-overflow-trigger) {
border-color: #334155;
background: #172033;
color: #dbe5f1;
@@ -2047,6 +2450,59 @@ useDesktopShortcuts(
background: #111827;
}
:global([data-ctms-theme="dark"] .workspace-tabs-overflow-count) {
background: #243247;
color: #bfdbfe;
}
:global([data-ctms-theme="dark"] .workspace-tabs-overflow-popper .el-dropdown-menu) {
background: transparent;
}
:global([data-ctms-theme="dark"] .workspace-tabs-overflow-popper.el-popper) {
border-color: #334155;
background: #172033;
}
:global([data-ctms-theme="dark"] .workspace-tabs-overflow-popper .el-popper__arrow::before) {
border-color: #334155;
background: #172033;
}
:global([data-ctms-theme="dark"] .workspace-tabs-overflow-header) {
border-bottom-color: #334155;
color: #e5edf7;
}
:global([data-ctms-theme="dark"] .workspace-tabs-overflow-header span:last-child) {
color: #94a3b8;
}
:global([data-ctms-theme="dark"] .workspace-tabs-overflow-popper .el-dropdown-menu__item) {
color: #dbe5f1;
}
:global([data-ctms-theme="dark"] .workspace-tabs-overflow-popper .el-dropdown-menu__item:hover),
:global([data-ctms-theme="dark"] .workspace-tabs-overflow-popper .el-dropdown-menu__item:focus),
:global([data-ctms-theme="dark"] .workspace-tabs-overflow-popper .el-dropdown-menu__item.is-current) {
background: #243247;
color: #bfdbfe;
}
:global([data-ctms-theme="dark"] .workspace-tabs-overflow-item-mark) {
background: #6f87a1;
}
:global([data-ctms-theme="dark"] .workspace-tabs-overflow-close) {
color: #94a3b8;
}
:global([data-ctms-theme="dark"] .workspace-tabs-overflow-close:hover),
:global([data-ctms-theme="dark"] .workspace-tabs-overflow-close:focus-visible) {
background: rgba(226, 232, 240, 0.1);
color: #f8fafc;
}
:global([data-ctms-theme="dark"] .desktop-content) {
background: #0f172a;
}