style: 优化个人中心和偏好设置弹窗样式,重构工作入口为精致分屏布局并移除首字徽标

This commit is contained in:
Cheng Zhou
2026-07-08 20:20:41 +08:00
parent e7b18758b2
commit b73f23c1eb
33 changed files with 2323 additions and 379 deletions
+62 -86
View File
@@ -5,25 +5,7 @@
<div class="sidebar-title-row">
<h1>{{ TEXT.common.appName }}</h1>
<el-dropdown v-if="study.currentStudy" trigger="click" class="study-switcher" @command="handleStudySwitch">
<button class="study-switcher-trigger" type="button">
<span class="study-name">{{ study.currentStudy.name }}</span>
<el-icon><ArrowDown /></el-icon>
</button>
<template #dropdown>
<el-dropdown-menu class="study-switcher-menu">
<el-dropdown-item
v-for="item in studies"
:key="item.id"
:command="item"
:class="{ active: study.currentStudy?.id === item.id }"
>
{{ item.name }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<button v-else class="study-switcher-trigger empty" type="button" @click="router.push('/admin/projects')">
<button v-if="!study.currentStudy" class="study-switcher-trigger empty" type="button" @click="openDesktopProjectEntry">
选择项目
</button>
</div>
@@ -87,7 +69,7 @@
</section>
<section v-if="projectNavigationItems.length" class="nav-section">
<div class="section-label">{{ TEXT.menu.currentProject }}</div>
<div class="section-label">{{ desktopProjectSectionLabel }}</div>
<template v-for="item in projectNavigationItems" :key="item.path">
<button
v-if="!item.children?.length"
@@ -269,6 +251,10 @@
<el-icon><User /></el-icon>
<span>{{ TEXT.menu.profile }}</span>
</el-dropdown-item>
<el-dropdown-item command="projectEntry">
<el-icon><Suitcase /></el-icon>
<span>{{ projectEntryMenuLabel }}</span>
</el-dropdown-item>
<el-dropdown-item command="desktopPreferences">
<el-icon><Monitor /></el-icon>
<span>系统偏好</span>
@@ -409,11 +395,11 @@ import {
import { ElMessageBox } from "element-plus";
import { useAuthStore } from "../store/auth";
import { useStudyStore } from "../store/study";
import { fetchStudies } from "../api/studies";
import { fetchOverdueAesCount } from "../api/dashboard";
import { listMonitoringVisitIssues } from "../api/monitoringVisitIssues";
import { TEXT } from "../locales";
import { forceLogout, LOGOUT_REASON_MANUAL } from "../session/sessionManager";
import { DESKTOP_SESSION_RESTORE_PATH, shouldPreserveDesktopSessionOnAuthCheckFailure } from "../session/authRecovery";
import {
checkDesktopUpdateAndPrompt,
getDesktopUpdateStatus,
@@ -475,7 +461,6 @@ const profileDialogDirty = ref(false);
const desktopServerUrl = ref(getDesktopServerUrl());
const desktopUpdateStatus = ref<DesktopUpdateStatusSnapshot>(getDesktopUpdateStatus());
const desktopActivities = ref<DesktopActivityItem[]>(getDesktopActivities());
const studies = ref<any[]>([]);
const desktopFavoriteRoutes = ref<DesktopRoutePreference[]>(readDesktopFavoriteRoutes());
const workspaceTabs = ref<DesktopRoutePreference[]>([]);
const draggingWorkspaceTabPath = ref("");
@@ -535,11 +520,13 @@ const toggleNavigationGroup = (item: LayoutNavigationItem) => {
expandedNavigationGroups.value = next;
};
const adminNavigationItems = computed(() =>
buildAdminNavigationItems({
hasUser: Boolean(auth.user),
isAdmin: isAdmin.value,
canAccessAdminPermissions: canAccessAdminPermissions.value,
}),
study.currentStudy
? []
: buildAdminNavigationItems({
hasUser: Boolean(auth.user),
isAdmin: isAdmin.value,
canAccessAdminPermissions: canAccessAdminPermissions.value,
}),
);
const projectNavigationItems = computed(() =>
buildProjectNavigationItems({
@@ -561,6 +548,8 @@ const userDisplayInitial = computed(() => {
const source = auth.user?.full_name || auth.user?.username || auth.user?.email || TEXT.common.labels.userInitialFallback;
return source.charAt(0).toUpperCase();
});
const desktopProjectSectionLabel = computed(() => study.currentStudy?.name || TEXT.menu.currentProject);
const projectEntryMenuLabel = computed(() => (study.currentStudy ? "返回项目选择" : "选择项目"));
const desktopConnectionLabel = computed(() => (desktopServerUrl.value ? "已连接" : "未配置"));
const desktopConnectionClass = computed(() => (desktopServerUrl.value ? "is-connected" : "is-warning"));
const desktopUpdateNoticeVisible = computed(
@@ -755,7 +744,7 @@ const closeWorkspaceTab = (path: string) => {
if (next) {
router.push(next.path);
} else {
router.push(study.currentStudy ? "/project/overview" : "/admin/projects");
router.push(study.currentStudy ? "/project/overview" : "/desktop/project-entry");
}
};
@@ -793,6 +782,13 @@ const openDesktopPreferences = () => {
desktopPreferencesVisible.value = true;
};
const openDesktopProjectEntry = async () => {
study.clearCurrentStudy();
workspaceTabs.value = [];
lastClosedWorkspaceTab.value = null;
await router.push("/desktop/project-entry");
};
const toggleCurrentFavorite = () => {
const current = currentDesktopRoutePreference.value;
if (!current) return;
@@ -842,18 +838,6 @@ const desktopCommands = computed<DesktopCommand[]>(() => {
void router.push(item.path);
},
}));
const projectCommands: DesktopCommand[] = studies.value.map((item) => ({
id: `study:${item.id}`,
title: `切换项目:${item.name}`,
group: "项目",
keywords: [item.name, item.project_no, item.protocol_no].filter(Boolean),
visible: Boolean(item?.id),
run: async () => {
study.setCurrentStudy(item);
await study.loadCurrentStudyPermissions().catch(() => {});
await router.push("/project/overview");
},
}));
const workspaceTabCommands: DesktopCommand[] = workspaceTabs.value.map((item) => ({
id: `workspace-tab:${item.path}`,
title: `切换标签:${item.title}`,
@@ -895,6 +879,13 @@ const desktopCommands = computed<DesktopCommand[]>(() => {
await checkDesktopUpdateAndPrompt({ notifyWhenCurrent: true });
},
},
{
id: "desktop:project-entry",
title: projectEntryMenuLabel.value,
group: "桌面",
visible: true,
run: openDesktopProjectEntry,
},
{
id: "desktop:favorite-current",
title: currentRouteFavorited.value ? "取消收藏当前模块" : "收藏当前模块",
@@ -911,17 +902,9 @@ const desktopCommands = computed<DesktopCommand[]>(() => {
},
...workspaceTabCommands,
...navigationCommands,
...projectCommands,
];
});
const loadStudies = async () => {
try {
const { data } = await fetchStudies();
studies.value = Array.isArray(data) ? data : (data as any).items || [];
} catch {}
};
const loadHeaderReminders = async () => {
const studyId = study.currentStudy?.id;
if (!studyId || isAdminContext.value || !hasAnyRiskReminderAccess.value) {
@@ -958,13 +941,6 @@ const handleReminderCommand = (cmd: string) => {
}
};
const handleStudySwitch = async (item: any) => {
if (!item?.id || item.id === study.currentStudy?.id) return;
study.setCurrentStudy(item);
await study.loadCurrentStudyPermissions().catch(() => {});
await router.push("/project/overview");
};
const logoutImmediately = async () => {
await auth.logout();
study.clearCurrentStudy();
@@ -999,6 +975,8 @@ const onCommand = (cmd: string) => {
onLogout();
} else if (cmd === "profile") {
profileDialogVisible.value = true;
} else if (cmd === "projectEntry") {
openDesktopProjectEntry();
} else if (cmd === "desktopPreferences") {
openDesktopPreferences();
}
@@ -1072,13 +1050,16 @@ onMounted(async () => {
});
if (auth.token && !auth.user) {
try {
await auth.fetchMe();
} catch {
await auth.fetchMe({ disableNetworkRetry: true, suppressErrorMessage: true });
} catch (error) {
if (shouldPreserveDesktopSessionOnAuthCheckFailure(error)) {
await router.replace({ path: DESKTOP_SESSION_RESTORE_PATH, query: { redirect: route.fullPath } });
return;
}
await logoutImmediately();
return;
}
}
loadStudies();
loadHeaderReminders();
});
@@ -1191,13 +1172,6 @@ useDesktopShortcuts(
color: #172233;
}
.study-switcher {
justify-self: start;
width: 140px;
max-width: 100%;
min-width: 0;
}
.study-switcher-trigger {
appearance: none;
display: grid;
@@ -1225,20 +1199,6 @@ useDesktopShortcuts(
font-weight: 700;
}
.study-name {
overflow: hidden;
font-size: 13px;
font-weight: 760;
text-overflow: ellipsis;
white-space: nowrap;
}
.study-switcher-menu :deep(.el-dropdown-menu__item.active) {
color: #24496f;
background: #edf4fb;
font-weight: 700;
}
.sidebar-scroll {
flex: 1;
min-height: 0;
@@ -1958,12 +1918,28 @@ useDesktopShortcuts(
transform: translateY(-3px);
}
:global(.profile-settings-dialog),
/* profile-settings-dialog:移除外层卡片外壳,只保留容器行为 */
:global(.profile-settings-dialog) {
--el-dialog-bg-color: transparent !important;
--el-dialog-box-shadow: none !important;
--el-dialog-border-radius: 0 !important;
overflow: visible !important;
background: transparent !important;
box-shadow: none !important;
border-radius: 0 !important;
border: none !important;
}
:global(.desktop-preferences-dialog) {
overflow: hidden;
border-radius: 14px;
/* dialog 容器本身设置侧边栏同色背景,彻底兜底 */
background: #f2f4f7 !important;
/* 移除外层卡片外壳,内容区自带圆角阴影 */
--el-dialog-bg-color: transparent !important;
--el-dialog-box-shadow: none !important;
--el-dialog-border-radius: 0 !important;
overflow: visible !important;
background: transparent !important;
box-shadow: none !important;
border-radius: 0 !important;
border: none !important;
}
:global(.profile-settings-dialog .el-dialog__header),
@@ -1973,12 +1949,12 @@ useDesktopShortcuts(
:global(.profile-settings-dialog .el-dialog__body) {
padding: 0;
background: transparent;
}
:global(.desktop-preferences-dialog .el-dialog__body) {
/* 背景与侧边栏一致,避免内容高度不足时白色透出 */
padding: 0;
background: #f2f4f7;
background: transparent;
display: flex;
flex-direction: column;
}