feat(collaboration): 完善在线文档协作与通知闭环
- 新增协作文件夹、文件、不可变修订、成员、会话、回调回执、编辑申请与分享链接数据模型。 - 补齐新建、导入、复制、下载、回收站、恢复、成员授权、所有权转让及文件级权限接口。 - 接入 ONLYOFFICE 共同编辑、历史版本预览与恢复、修订另存副本、导出下载审计和幂等回调保存。 - 增加编辑权限申请、审批通知、项目提醒聚合、通知 Feed、已读处理及历史待办数据回填。 - 支持公开分享的查看或编辑模式、有效期、密码哈希、失败锁定、短时访问凭证与固定分享地址。 - 增加协作者导出、申请编辑、工作表结构保护和所有权管理策略,并纳入项目接口权限矩阵。 - 新增协作文件库、编辑工作区、公开分享页、下载与另存为对话框,以及导航、路由和权限入口。 - 统一网页端与桌面端通知布局,增加沉浸式工作区和浏览器、Tauri 双端全屏能力。 - 扩展运行时文件下载适配、Tauri 环境识别和原生全屏命令,继续保持业务代码运行时边界。 - 加固 ONLYOFFICE 消息桥的同源下载、签名地址隔离和保存为能力校验,并更新桌面发布检查。 - 增加连续数据库迁移、50MB 上传限制、OnlyOffice 中文文案与开发启动路由校验。 - 补充协作、通知、权限、路由、运行时、布局和 OnlyOffice 相关测试及模块说明文档。
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
<template>
|
||||
<el-container
|
||||
class="layout-container web-layout-container"
|
||||
:class="{ 'mobile-nav-open': isMobileNavOpen, 'mobile-layout': isMobileViewport }"
|
||||
:class="{ 'mobile-nav-open': isMobileNavOpen, 'mobile-layout': isMobileViewport, 'is-immersive-workspace': route.meta.immersiveWorkspace }"
|
||||
@keydown.esc="closeMobileNav"
|
||||
>
|
||||
<el-aside
|
||||
v-if="!route.meta.immersiveWorkspace"
|
||||
:width="isMobileViewport ? '280px' : (isCollapsed ? '0px' : '200px')"
|
||||
class="layout-aside"
|
||||
:class="{ collapsed: isCollapsed, 'mobile-open': isMobileNavOpen }"
|
||||
@@ -121,7 +122,7 @@
|
||||
<el-icon><Flag /></el-icon>
|
||||
<span>{{ TEXT.menu.etmf }}</span>
|
||||
</el-menu-item>
|
||||
<el-sub-menu v-if="canAccessAnyProjectPath(['/knowledge/medical-consult', '/knowledge/precautions', '/knowledge/support-files', '/knowledge/instruction-files'])" index="knowledge">
|
||||
<el-sub-menu v-if="canAccessAnyProjectPath(['/knowledge/medical-consult', '/knowledge/precautions', '/knowledge/support-files', '/knowledge/instruction-files', '/knowledge/collaboration'])" index="knowledge">
|
||||
<template #title>
|
||||
<el-icon><Notebook /></el-icon>
|
||||
<span>{{ TEXT.menu.sharedLibrary }}</span>
|
||||
@@ -130,13 +131,14 @@
|
||||
<el-menu-item v-if="canAccessProjectPath('/knowledge/precautions')" index="/knowledge/precautions">{{ TEXT.menu.knowledgeNotes }}</el-menu-item>
|
||||
<el-menu-item v-if="canAccessProjectPath('/knowledge/support-files')" index="/knowledge/support-files">{{ TEXT.menu.knowledgeSupportFiles }}</el-menu-item>
|
||||
<el-menu-item v-if="canAccessProjectPath('/knowledge/instruction-files')" index="/knowledge/instruction-files">{{ TEXT.menu.knowledgeInstructionFiles }}</el-menu-item>
|
||||
<el-menu-item v-if="canAccessProjectPath('/knowledge/collaboration')" index="/knowledge/collaboration">{{ TEXT.menu.knowledgeCollaboration }}</el-menu-item>
|
||||
</el-sub-menu>
|
||||
</el-menu-item-group>
|
||||
</el-menu>
|
||||
</el-aside>
|
||||
|
||||
<el-container class="main-container">
|
||||
<el-header class="layout-header">
|
||||
<el-header v-if="!route.meta.immersiveWorkspace" class="layout-header">
|
||||
<div class="header-left">
|
||||
<el-button
|
||||
class="collapse-btn"
|
||||
@@ -189,11 +191,6 @@
|
||||
</div>
|
||||
|
||||
<div class="header-right">
|
||||
<button v-if="hasProjectContext" class="workbench-return-button" type="button" @click="openWorkbenchEntry">
|
||||
<el-icon><Suitcase /></el-icon>
|
||||
<span>工作台</span>
|
||||
</button>
|
||||
|
||||
<button v-if="isDesktop" class="desktop-command-trigger" type="button" @click="openCommandPalette">
|
||||
<el-icon><Search /></el-icon>
|
||||
<span>搜索命令</span>
|
||||
@@ -233,7 +230,25 @@
|
||||
</button>
|
||||
</el-tooltip>
|
||||
|
||||
<el-dropdown trigger="click" placement="bottom-end" class="header-reminder-dropdown" @command="handleReminderCommand">
|
||||
<el-tooltip v-if="webFullscreenAvailable" :content="webFullscreenLabel" placement="bottom">
|
||||
<button
|
||||
class="header-fullscreen-button"
|
||||
type="button"
|
||||
:aria-label="webFullscreenLabel"
|
||||
:aria-pressed="webFullscreenActive"
|
||||
@click="handleWebFullscreenToggle"
|
||||
>
|
||||
<el-icon><ScaleToOriginal v-if="webFullscreenActive" /><FullScreen v-else /></el-icon>
|
||||
</button>
|
||||
</el-tooltip>
|
||||
|
||||
<el-dropdown
|
||||
trigger="click"
|
||||
placement="bottom-end"
|
||||
class="header-reminder-dropdown"
|
||||
@command="handleReminderCommand"
|
||||
@visible-change="handleReminderVisibilityChange"
|
||||
>
|
||||
<button class="header-reminder-button" :aria-label="TEXT.common.labels.projectReminders" type="button">
|
||||
<el-badge
|
||||
v-if="headerReminderTotal > 0"
|
||||
@@ -261,27 +276,20 @@
|
||||
:key="item.key"
|
||||
:command="item.command"
|
||||
class="header-reminder-item"
|
||||
:class="{ 'is-read': item.isRead }"
|
||||
>
|
||||
<span class="header-reminder-item-mark" :class="`is-${item.tone}`"></span>
|
||||
<span class="header-reminder-item-main">
|
||||
<span class="header-reminder-item-title">{{ item.title }}</span>
|
||||
<span class="header-reminder-item-desc">{{ item.description }}</span>
|
||||
</span>
|
||||
<span class="header-reminder-item-count">{{ item.count }}</span>
|
||||
<span class="header-reminder-item-count">{{ item.timeLabel }}</span>
|
||||
</el-dropdown-item>
|
||||
</template>
|
||||
<div v-else class="header-reminder-empty">
|
||||
{{ headerRemindersLoading ? TEXT.common.loading : TEXT.common.labels.projectRemindersEmpty }}
|
||||
</div>
|
||||
|
||||
<el-dropdown-item
|
||||
v-if="hasAnyRiskReminderAccess"
|
||||
command="riskIssues"
|
||||
divided
|
||||
class="header-reminder-footer"
|
||||
>
|
||||
{{ TEXT.common.actions.view }}{{ TEXT.menu.riskIssues }}
|
||||
</el-dropdown-item>
|
||||
</div>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
@@ -344,7 +352,7 @@
|
||||
</el-container>
|
||||
|
||||
<button
|
||||
v-if="isMobileNavOpen"
|
||||
v-if="isMobileNavOpen && !route.meta.immersiveWorkspace"
|
||||
type="button"
|
||||
class="mobile-nav-backdrop"
|
||||
aria-label="关闭导航菜单"
|
||||
@@ -396,15 +404,13 @@ import { useRoute, useRouter } from "vue-router";
|
||||
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 {
|
||||
User, Suitcase, House, Calendar, Flag,
|
||||
CircleCheck, Box, Coin, Notebook, Document, ArrowDown, SwitchButton, Files, Key, DataAnalysis, UserFilled, Bell, Setting,
|
||||
Search, Star, StarFilled, Clock, Monitor
|
||||
Search, Star, StarFilled, Clock, Monitor, FullScreen, ScaleToOriginal
|
||||
} from "@element-plus/icons-vue";
|
||||
import { ElMessageBox } from "element-plus";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { getProjectRoutePermission, hasProjectPermission, projectRouteLandingPaths } from "../utils/projectRoutePermissions";
|
||||
import { isApiPermissionAllowed } from "../utils/apiPermissionValue";
|
||||
import { forceLogout, LOGOUT_REASON_MANUAL } from "../session/sessionManager";
|
||||
@@ -415,9 +421,14 @@ import {
|
||||
getAppMetadata,
|
||||
getDesktopServerUrl,
|
||||
isTauriRuntime,
|
||||
isWebFullscreenActive,
|
||||
isWebFullscreenAvailable,
|
||||
listenWebFullscreenChange,
|
||||
refreshWebFullscreenState,
|
||||
listenDesktopMenuCommand,
|
||||
readDesktopFavoriteRoutes,
|
||||
toggleDesktopFavoriteRoute,
|
||||
toggleWebFullscreen,
|
||||
type DesktopRoutePreference,
|
||||
} from "../runtime";
|
||||
import { dispatchDesktopRefreshCurrentView } from "../composables/useDesktopRefresh";
|
||||
@@ -428,6 +439,7 @@ import AccountConnectionStatus from "./AccountConnectionStatus.vue";
|
||||
import ProfileSettings from "../views/ProfileSettings.vue";
|
||||
import DesktopPreferences from "../views/DesktopPreferences.vue";
|
||||
import { getActiveLayoutPath } from "./layout/navigation";
|
||||
import { useProjectNotifications } from "../composables/useProjectNotifications";
|
||||
|
||||
const auth = useAuthStore();
|
||||
const study = useStudyStore();
|
||||
@@ -459,14 +471,23 @@ const profileDialogVisible = ref(false);
|
||||
const profileDialogDirty = ref(false);
|
||||
const commandPaletteVisible = ref(false);
|
||||
const desktopPreferencesVisible = ref(false);
|
||||
const webFullscreenAvailable = ref(false);
|
||||
const webFullscreenActive = ref(false);
|
||||
const desktopServerUrl = ref(getDesktopServerUrl());
|
||||
const desktopFavoriteRoutes = ref<DesktopRoutePreference[]>(readDesktopFavoriteRoutes());
|
||||
const headerRemindersLoading = ref(false);
|
||||
const headerReminderStats = ref({
|
||||
overdueAes: 0,
|
||||
overdueMonitoringIssues: 0,
|
||||
});
|
||||
const {
|
||||
headerRemindersLoading,
|
||||
headerReminderItems,
|
||||
headerReminderTotal,
|
||||
headerReminderBadgeValue,
|
||||
loadHeaderReminders,
|
||||
handleReminderCommand,
|
||||
handleReminderVisibilityChange,
|
||||
startHeaderNotificationPolling,
|
||||
stopHeaderNotificationPolling,
|
||||
} = useProjectNotifications();
|
||||
let desktopMenuUnlisten: (() => void) | undefined;
|
||||
let webFullscreenUnlisten: (() => void) | undefined;
|
||||
let mobileViewportChangeHandler: ((event: MediaQueryListEvent) => void) | undefined;
|
||||
const isAdminContext = computed(() => route.path.startsWith("/admin"));
|
||||
const hasProjectContext = computed(() => Boolean(study.currentStudy) && !isAdminContext.value);
|
||||
@@ -492,76 +513,6 @@ const userDisplayInitial = computed(() => {
|
||||
});
|
||||
|
||||
const showAdminNavigation = computed(() => Boolean(auth.user) && !hasProjectContext.value);
|
||||
const canReadRiskIssueAes = computed(() => hasProjectContext.value && canAccessProjectPath("/risk-issues/sae"));
|
||||
const canReadMonitoringIssues = computed(() => hasProjectContext.value && canAccessProjectPath("/risk-issues/monitoring-visits"));
|
||||
const hasAnyRiskReminderAccess = computed(() => canReadRiskIssueAes.value || canReadMonitoringIssues.value);
|
||||
|
||||
const toHeaderNumber = (value: unknown) => {
|
||||
const num = typeof value === "number" ? value : Number(value);
|
||||
return Number.isFinite(num) && num > 0 ? num : 0;
|
||||
};
|
||||
|
||||
const loadHeaderReminders = async () => {
|
||||
const studyId = study.currentStudy?.id;
|
||||
if (!studyId || !hasProjectContext.value || !hasAnyRiskReminderAccess.value) {
|
||||
headerReminderStats.value = { overdueAes: 0, overdueMonitoringIssues: 0 };
|
||||
return;
|
||||
}
|
||||
|
||||
headerRemindersLoading.value = true;
|
||||
try {
|
||||
const [aesRes, monitoringRes] = await Promise.allSettled([
|
||||
canReadRiskIssueAes.value ? fetchOverdueAesCount(studyId) : Promise.resolve(null),
|
||||
canReadMonitoringIssues.value ? listMonitoringVisitIssues(studyId, { overdue: true, limit: 500 }) : Promise.resolve(null),
|
||||
]);
|
||||
if (study.currentStudy?.id !== studyId || isAdminContext.value) return;
|
||||
headerReminderStats.value = {
|
||||
overdueAes: aesRes.status === "fulfilled" && aesRes.value ? toHeaderNumber((aesRes.value.data as any)?.total) : 0,
|
||||
overdueMonitoringIssues: monitoringRes.status === "fulfilled" && monitoringRes.value && Array.isArray(monitoringRes.value.data)
|
||||
? monitoringRes.value.data.length
|
||||
: 0,
|
||||
};
|
||||
} finally {
|
||||
if (study.currentStudy?.id === studyId) {
|
||||
headerRemindersLoading.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const headerReminderItems = computed(() => [
|
||||
canReadRiskIssueAes.value && headerReminderStats.value.overdueAes > 0 ? {
|
||||
key: "overdueAes",
|
||||
command: "overdueAes",
|
||||
title: TEXT.common.labels.overdueAes,
|
||||
description: TEXT.common.labels.overdueAesDesc,
|
||||
count: headerReminderStats.value.overdueAes,
|
||||
tone: "danger",
|
||||
} : null,
|
||||
canReadMonitoringIssues.value && headerReminderStats.value.overdueMonitoringIssues > 0 ? {
|
||||
key: "overdueMonitoringIssues",
|
||||
command: "overdueMonitoringIssues",
|
||||
title: TEXT.common.labels.overdueMonitoringIssues,
|
||||
description: TEXT.common.labels.overdueMonitoringIssuesDesc,
|
||||
count: headerReminderStats.value.overdueMonitoringIssues,
|
||||
tone: "warning",
|
||||
} : null,
|
||||
].filter(Boolean) as Array<{
|
||||
key: string;
|
||||
command: string;
|
||||
title: string;
|
||||
description: string;
|
||||
count: number;
|
||||
tone: "danger" | "warning";
|
||||
}>);
|
||||
|
||||
const headerReminderTotal = computed(() =>
|
||||
headerReminderItems.value.reduce((sum, item) => sum + item.count, 0)
|
||||
);
|
||||
|
||||
const headerReminderBadgeValue = computed(() =>
|
||||
headerReminderTotal.value > 99 ? "99+" : headerReminderTotal.value
|
||||
);
|
||||
|
||||
const accountProjectRoleLabel = computed(() => {
|
||||
if (!hasProjectContext.value || !study.currentStudy) return "";
|
||||
const roleCode = (projectRole.value || "").toUpperCase();
|
||||
@@ -615,6 +566,7 @@ const desktopNavigationItems = computed<DesktopNavigationItem[]>(() => {
|
||||
{ label: TEXT.menu.knowledgeNotes, path: "/knowledge/precautions", group: TEXT.menu.sharedLibrary, keywords: ["knowledge", "note"] },
|
||||
{ label: TEXT.menu.knowledgeSupportFiles, path: "/knowledge/support-files", group: TEXT.menu.sharedLibrary, keywords: ["knowledge", "support"] },
|
||||
{ label: TEXT.menu.knowledgeInstructionFiles, path: "/knowledge/instruction-files", group: TEXT.menu.sharedLibrary, keywords: ["knowledge", "instruction"] },
|
||||
{ label: TEXT.menu.knowledgeCollaboration, path: "/knowledge/collaboration", group: TEXT.menu.sharedLibrary, keywords: ["knowledge", "collaboration", "onlyoffice", "协作"] },
|
||||
];
|
||||
items.push(...projectItems.filter((item) => canAccessProjectPath(item.path)));
|
||||
}
|
||||
@@ -851,6 +803,7 @@ const breadcrumbs = computed(() => {
|
||||
{ label: TEXT.menu.knowledgeNotes, path: "/knowledge/precautions" },
|
||||
{ label: TEXT.menu.knowledgeSupportFiles, path: "/knowledge/support-files" },
|
||||
{ label: TEXT.menu.knowledgeInstructionFiles, path: "/knowledge/instruction-files" },
|
||||
{ label: TEXT.menu.knowledgeCollaboration, path: "/knowledge/collaboration" },
|
||||
]},
|
||||
];
|
||||
|
||||
@@ -940,25 +893,6 @@ const handleMenuSelect = (index: string) => {
|
||||
router.push(index);
|
||||
};
|
||||
|
||||
const handleReminderCommand = (cmd: string) => {
|
||||
if (cmd === "overdueAes" && canReadRiskIssueAes.value) {
|
||||
router.push("/risk-issues/sae");
|
||||
return;
|
||||
}
|
||||
if (cmd === "overdueMonitoringIssues" && canReadMonitoringIssues.value) {
|
||||
router.push("/risk-issues/monitoring-visits");
|
||||
return;
|
||||
}
|
||||
if (cmd === "riskIssues") {
|
||||
const path = canReadRiskIssueAes.value
|
||||
? "/risk-issues/sae"
|
||||
: canReadMonitoringIssues.value
|
||||
? "/risk-issues/monitoring-visits"
|
||||
: "";
|
||||
if (path) router.push(path);
|
||||
}
|
||||
};
|
||||
|
||||
watch(() => route.path, () => {
|
||||
closeMobileNav();
|
||||
study.setViewContext(null);
|
||||
@@ -987,6 +921,28 @@ const closeMobileNav = () => {
|
||||
isMobileNavOpen.value = false;
|
||||
};
|
||||
|
||||
const webFullscreenLabel = computed(() => webFullscreenActive.value ? "退出全屏" : "进入全屏");
|
||||
|
||||
const syncWebFullscreenState = () => {
|
||||
webFullscreenAvailable.value = isWebFullscreenAvailable();
|
||||
webFullscreenActive.value = isWebFullscreenActive();
|
||||
};
|
||||
|
||||
const handleWebFullscreenToggle = async () => {
|
||||
try {
|
||||
await toggleWebFullscreen();
|
||||
syncWebFullscreenState();
|
||||
} catch (error) {
|
||||
const message = isTauriRuntime()
|
||||
? "无法切换桌面全屏,请使用“显示 > 进入全屏”重试"
|
||||
: typeof error === "object" && error !== null && "name" in error && error.name === "NotAllowedError"
|
||||
? "浏览器未允许进入全屏,请再次点击或检查站点权限"
|
||||
: "无法切换全屏,请使用浏览器菜单重试";
|
||||
ElMessage.warning(message);
|
||||
syncWebFullscreenState();
|
||||
}
|
||||
};
|
||||
|
||||
const syncMobileViewport = (matches: boolean) => {
|
||||
isMobileViewport.value = matches;
|
||||
if (!matches) closeMobileNav();
|
||||
@@ -1036,6 +992,9 @@ const onLogout = async () => {
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
syncWebFullscreenState();
|
||||
webFullscreenUnlisten = listenWebFullscreenChange(syncWebFullscreenState);
|
||||
void refreshWebFullscreenState().then(syncWebFullscreenState).catch(() => {});
|
||||
if (typeof window.matchMedia === "function") {
|
||||
mobileViewportMediaQuery = window.matchMedia("(max-width: 767px)");
|
||||
syncMobileViewport(mobileViewportMediaQuery.matches);
|
||||
@@ -1059,14 +1018,17 @@ onMounted(async () => {
|
||||
}
|
||||
loadStudies();
|
||||
loadHeaderReminders();
|
||||
startHeaderNotificationPolling();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
webFullscreenUnlisten?.();
|
||||
window.removeEventListener(DESKTOP_SERVER_URL_CHANGED_EVENT, updateDesktopServerUrl);
|
||||
if (mobileViewportMediaQuery && mobileViewportChangeHandler) {
|
||||
mobileViewportMediaQuery.removeEventListener("change", mobileViewportChangeHandler);
|
||||
}
|
||||
desktopMenuUnlisten?.();
|
||||
stopHeaderNotificationPolling();
|
||||
});
|
||||
|
||||
const onCommand = (cmd: string) => {
|
||||
@@ -1572,26 +1534,6 @@ useDesktopShortcuts(
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.workbench-return-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
height: 32px;
|
||||
padding: 0 10px;
|
||||
border: 1px solid #c7d7ec;
|
||||
border-radius: 8px;
|
||||
background: #eff6ff;
|
||||
color: #1d4ed8;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.workbench-return-button:hover {
|
||||
border-color: #93c5fd;
|
||||
background: #dbeafe;
|
||||
}
|
||||
|
||||
.desktop-command-trigger {
|
||||
display: inline-grid;
|
||||
grid-template-columns: auto minmax(0, auto) auto;
|
||||
@@ -1743,6 +1685,7 @@ useDesktopShortcuts(
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-fullscreen-button,
|
||||
.header-reminder-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -1758,6 +1701,7 @@ useDesktopShortcuts(
|
||||
transition: background-color 0.18s ease, border-color 0.18s ease, color 0.18s ease, box-shadow 0.18s ease;
|
||||
}
|
||||
|
||||
.header-fullscreen-button:hover,
|
||||
.header-reminder-button:hover {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border-color: rgba(0, 0, 0, 0.14);
|
||||
@@ -1765,6 +1709,7 @@ useDesktopShortcuts(
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.header-fullscreen-button .el-icon,
|
||||
.header-reminder-button .el-icon {
|
||||
font-size: 17px;
|
||||
}
|
||||
@@ -1835,6 +1780,7 @@ useDesktopShortcuts(
|
||||
padding: 8px 7px !important;
|
||||
border-radius: 7px;
|
||||
}
|
||||
:global(.header-reminder-item.is-read) { opacity: 0.62 !important; }
|
||||
|
||||
.header-reminder-item :deep(.el-dropdown-menu__item) {
|
||||
width: 100%;
|
||||
@@ -1854,6 +1800,7 @@ useDesktopShortcuts(
|
||||
.header-reminder-item-mark.is-warning {
|
||||
background: #f59e0b;
|
||||
}
|
||||
.header-reminder-item-mark.is-info { background: var(--ctms-primary); }
|
||||
|
||||
.header-reminder-item-main {
|
||||
display: inline-flex;
|
||||
@@ -1881,9 +1828,10 @@ useDesktopShortcuts(
|
||||
}
|
||||
|
||||
.header-reminder-item-count {
|
||||
color: #0f172a;
|
||||
font-size: 16px;
|
||||
font-weight: 750;
|
||||
color: #7c8da3;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.header-reminder-empty {
|
||||
@@ -2111,17 +2059,6 @@ useDesktopShortcuts(
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.workbench-return-button {
|
||||
width: 32px;
|
||||
padding: 0;
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
.workbench-return-button .el-icon {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.header-account-role,
|
||||
.user-profile .username,
|
||||
.user-profile .el-icon,
|
||||
@@ -2161,9 +2098,9 @@ useDesktopShortcuts(
|
||||
max-width: 42vw;
|
||||
}
|
||||
|
||||
.header-fullscreen-button,
|
||||
.header-reminder-button,
|
||||
.collapse-btn,
|
||||
.workbench-return-button,
|
||||
.dropdown-trigger.compact {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
|
||||
Reference in New Issue
Block a user