feat(ui): 升级顶栏为极简级联面包屑架构,自适应 320px 极窄视窗
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,7 +1,17 @@
|
||||
import React from 'react';
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { DeliverableItem, DeliverableCategory } from '../../types/workbench';
|
||||
import { DocLifecycleState } from '../../types/docgov';
|
||||
import { FolderKanban, FileDown, Maximize2, Minimize2, Lock, Globe, Shield } from 'lucide-react';
|
||||
import {
|
||||
FileDown,
|
||||
Maximize2,
|
||||
Minimize2,
|
||||
Lock,
|
||||
Globe,
|
||||
Shield,
|
||||
ChevronDown,
|
||||
Layers,
|
||||
Check,
|
||||
} from 'lucide-react';
|
||||
|
||||
interface SlimTabBarProps {
|
||||
deliverables: DeliverableItem[];
|
||||
@@ -26,6 +36,52 @@ export const SlimTabBar: React.FC<SlimTabBarProps> = ({
|
||||
isFullscreen,
|
||||
onToggleFullscreen,
|
||||
}) => {
|
||||
// 下拉菜单显示状态
|
||||
const [showCategoryMenu, setShowCategoryMenu] = useState<boolean>(false);
|
||||
const [showDocMenu, setShowDocMenu] = useState<boolean>(false);
|
||||
|
||||
// 容器宽度监听(用于右侧大盘按钮在空间充裕时展示文字,窄栏时优雅收缩为纯数字徽标)
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [containerWidth, setContainerWidth] = useState<number>(600);
|
||||
|
||||
useEffect(() => {
|
||||
const el = containerRef.current;
|
||||
if (!el) return;
|
||||
|
||||
const ro = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
setContainerWidth(entry.contentRect.width);
|
||||
}
|
||||
});
|
||||
ro.observe(el);
|
||||
return () => ro.disconnect();
|
||||
}, []);
|
||||
|
||||
// 点击外部及 Escape 键自动收起下拉浮层
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (e: MouseEvent) => {
|
||||
const target = e.target as HTMLElement;
|
||||
if (!target.closest('.dropdown-trigger') && !target.closest('.dropdown-menu')) {
|
||||
setShowCategoryMenu(false);
|
||||
setShowDocMenu(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
setShowCategoryMenu(false);
|
||||
setShowDocMenu(false);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('mousedown', handleClickOutside);
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => {
|
||||
window.removeEventListener('mousedown', handleClickOutside);
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const getStatusDot = (state: DocLifecycleState) => {
|
||||
switch (state) {
|
||||
case 'approved':
|
||||
@@ -47,6 +103,7 @@ export const SlimTabBar: React.FC<SlimTabBarProps> = ({
|
||||
const privateDocs = deliverables.filter((d) => d.category === 'private');
|
||||
|
||||
const visibleTabs = deliverables.filter((d) => d.category === activeCategory);
|
||||
const activeDoc = deliverables.find((d) => d.docId === activeDocId) || visibleTabs[0];
|
||||
|
||||
const handleCategoryClick = (cat: DeliverableCategory) => {
|
||||
onSelectCategory(cat);
|
||||
@@ -56,8 +113,38 @@ export const SlimTabBar: React.FC<SlimTabBarProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
// 分类属性映射
|
||||
const categoryConfig: Record<
|
||||
DeliverableCategory,
|
||||
{ label: string; icon: React.ReactNode; color: string; count: number }
|
||||
> = {
|
||||
workflow: {
|
||||
label: '流程受控',
|
||||
icon: <Shield size={12} />,
|
||||
color: 'var(--brand-500)',
|
||||
count: workflowDocs.length,
|
||||
},
|
||||
shared: {
|
||||
label: '项目共享',
|
||||
icon: <Globe size={12} />,
|
||||
color: 'var(--brand-500)',
|
||||
count: sharedDocs.length,
|
||||
},
|
||||
private: {
|
||||
label: '我的私密',
|
||||
icon: <Lock size={12} />,
|
||||
color: '#D97706',
|
||||
count: privateDocs.length,
|
||||
},
|
||||
};
|
||||
|
||||
const currentCatInfo = categoryConfig[activeCategory] || categoryConfig.workflow;
|
||||
const docStatus = activeDoc ? getStatusDot(activeDoc.lifecycleState) : null;
|
||||
const isCompact = containerWidth < 460;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
style={{
|
||||
height: '36px',
|
||||
borderBottom: '1px solid var(--border)',
|
||||
@@ -65,267 +152,321 @@ export const SlimTabBar: React.FC<SlimTabBarProps> = ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: '0 8px',
|
||||
gap: '8px',
|
||||
overflowX: 'auto',
|
||||
gap: '6px',
|
||||
flexShrink: 0,
|
||||
userSelect: 'none',
|
||||
position: 'relative',
|
||||
zIndex: 10,
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
{/* 分类切换分段 Pill */}
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
background: 'var(--bg-panel)',
|
||||
padding: '2px',
|
||||
borderRadius: '6px',
|
||||
border: '1px solid var(--border)',
|
||||
gap: '2px',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
{/* 1. 分类级联选择器 (Breadcrumb Category Step) */}
|
||||
<div style={{ position: 'relative', flexShrink: 0 }}>
|
||||
<button
|
||||
onClick={() => handleCategoryClick('workflow')}
|
||||
className="dropdown-trigger"
|
||||
onClick={() => {
|
||||
setShowCategoryMenu(!showCategoryMenu);
|
||||
setShowDocMenu(false);
|
||||
}}
|
||||
style={{
|
||||
height: '24px',
|
||||
padding: '0 8px',
|
||||
borderRadius: '4px',
|
||||
border: 'none',
|
||||
fontSize: '11px',
|
||||
fontWeight: activeCategory === 'workflow' ? 600 : 500,
|
||||
background: activeCategory === 'workflow' ? 'var(--brand-500)' : 'transparent',
|
||||
color: activeCategory === 'workflow' ? '#ffffff' : 'var(--text-muted)',
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '4px',
|
||||
transition: 'all 0.15s ease',
|
||||
}}
|
||||
title="研发体系预设受控交付物 (全员可见 · 严格审签)"
|
||||
>
|
||||
<Shield size={12} />
|
||||
<span>流程受控</span>
|
||||
<span
|
||||
style={{
|
||||
fontSize: '10px',
|
||||
padding: '0 4px',
|
||||
borderRadius: '8px',
|
||||
background: activeCategory === 'workflow' ? 'rgba(255,255,255,0.25)' : 'var(--bg-subtle)',
|
||||
color: activeCategory === 'workflow' ? '#ffffff' : 'var(--text-faint)',
|
||||
}}
|
||||
>
|
||||
{workflowDocs.length}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => handleCategoryClick('shared')}
|
||||
style={{
|
||||
height: '24px',
|
||||
padding: '0 8px',
|
||||
borderRadius: '4px',
|
||||
border: 'none',
|
||||
fontSize: '11px',
|
||||
fontWeight: activeCategory === 'shared' ? 600 : 500,
|
||||
background: activeCategory === 'shared' ? 'var(--brand-500)' : 'transparent',
|
||||
color: activeCategory === 'shared' ? '#ffffff' : 'var(--text-muted)',
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '4px',
|
||||
transition: 'all 0.15s ease',
|
||||
}}
|
||||
title="由成员对话推演并共享至项目的协作参考资料"
|
||||
>
|
||||
<Globe size={12} />
|
||||
<span>项目共享</span>
|
||||
<span
|
||||
style={{
|
||||
fontSize: '10px',
|
||||
padding: '0 4px',
|
||||
borderRadius: '8px',
|
||||
background: activeCategory === 'shared' ? 'rgba(255,255,255,0.25)' : 'var(--bg-subtle)',
|
||||
color: activeCategory === 'shared' ? '#ffffff' : 'var(--text-faint)',
|
||||
}}
|
||||
>
|
||||
{sharedDocs.length}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => handleCategoryClick('private')}
|
||||
style={{
|
||||
height: '24px',
|
||||
padding: '0 8px',
|
||||
borderRadius: '4px',
|
||||
border: 'none',
|
||||
fontSize: '11px',
|
||||
fontWeight: activeCategory === 'private' ? 600 : 500,
|
||||
background: activeCategory === 'private' ? '#D97706' : 'transparent',
|
||||
color: activeCategory === 'private' ? '#ffffff' : 'var(--text-muted)',
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '4px',
|
||||
transition: 'all 0.15s ease',
|
||||
}}
|
||||
title="您在对话中要求助手生成的私密文档 (仅您本人可见)"
|
||||
>
|
||||
<Lock size={12} />
|
||||
<span>我的私密</span>
|
||||
<span
|
||||
style={{
|
||||
fontSize: '10px',
|
||||
padding: '0 4px',
|
||||
borderRadius: '8px',
|
||||
background: activeCategory === 'private' ? 'rgba(255,255,255,0.25)' : 'var(--bg-subtle)',
|
||||
color: activeCategory === 'private' ? '#ffffff' : 'var(--text-faint)',
|
||||
}}
|
||||
>
|
||||
{privateDocs.length}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style={{ width: '1px', height: '18px', background: 'var(--border)', flexShrink: 0 }} />
|
||||
|
||||
{/* 交付物 Tabs (当前分类下的文档) */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '4px', flex: 1, overflowX: 'auto' }}>
|
||||
{visibleTabs.length === 0 ? (
|
||||
<div style={{ fontSize: '11px', color: 'var(--text-faint)', padding: '0 8px' }}>
|
||||
{activeCategory === 'private'
|
||||
? '暂无个人私密生成文档 (在对话中要求助手生成即可)'
|
||||
: activeCategory === 'shared'
|
||||
? '暂无项目共享参考资料'
|
||||
: '暂无流程文档'}
|
||||
</div>
|
||||
) : (
|
||||
visibleTabs.map((doc) => {
|
||||
const isActive = doc.docId === activeDocId;
|
||||
const status = getStatusDot(doc.lifecycleState);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={doc.docId}
|
||||
onClick={() => onSelectDoc(doc.docId)}
|
||||
style={{
|
||||
height: '28px',
|
||||
padding: '0 10px',
|
||||
borderRadius: '6px 6px 0 0',
|
||||
background: isActive ? 'var(--bg-panel)' : 'transparent',
|
||||
border: isActive ? '1px solid var(--border)' : '1px solid transparent',
|
||||
borderBottom: isActive ? '1px solid var(--bg-panel)' : '1px solid transparent',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '6px',
|
||||
fontSize: '12px',
|
||||
fontWeight: isActive ? 600 : 500,
|
||||
color: isActive ? 'var(--text-strong)' : 'var(--text-muted)',
|
||||
cursor: 'pointer',
|
||||
whiteSpace: 'nowrap',
|
||||
transition: 'all 0.15s ease',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
if (!isActive) e.currentTarget.style.color = 'var(--text)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
if (!isActive) e.currentTarget.style.color = 'var(--text-muted)';
|
||||
}}
|
||||
title={`${doc.title} (${doc.category === 'private' ? '仅我可见' : doc.category === 'shared' ? '项目共享' : status.label})`}
|
||||
>
|
||||
{doc.category === 'private' ? (
|
||||
<Lock size={12} color="#D97706" />
|
||||
) : doc.category === 'shared' ? (
|
||||
<Globe size={12} color="var(--brand-500)" />
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
width: '6px',
|
||||
height: '6px',
|
||||
borderRadius: '50%',
|
||||
background: status.color,
|
||||
boxShadow: `0 0 5px ${status.color}`,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<span style={{ maxWidth: '140px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
||||
{doc.title}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 右侧全局操作区 */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '4px', marginLeft: 'auto', flexShrink: 0 }}>
|
||||
{/* 全部交付物大盘抽屉入口 */}
|
||||
<button
|
||||
onClick={onToggleDeckDrawer}
|
||||
style={{
|
||||
height: '26px',
|
||||
padding: '0 8px',
|
||||
borderRadius: 'var(--r-sm)',
|
||||
background: 'var(--bg-panel)',
|
||||
borderRadius: '5px',
|
||||
border: '1px solid var(--border)',
|
||||
color: 'var(--text)',
|
||||
background: showCategoryMenu ? 'var(--bg-hover)' : 'var(--bg-panel)',
|
||||
color: currentCatInfo.color,
|
||||
fontSize: '11px',
|
||||
fontWeight: 600,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '4px',
|
||||
cursor: 'pointer',
|
||||
transition: 'background 0.15s ease',
|
||||
}}
|
||||
title="展开当前项目各阶段全部交付物状态大盘"
|
||||
title="点击切换交付物空间分类"
|
||||
>
|
||||
<FolderKanban size={13} style={{ color: 'var(--brand-500)' }} />
|
||||
<span>全景大盘 ({deliverables.length}) ▾</span>
|
||||
{currentCatInfo.icon}
|
||||
<span>{currentCatInfo.label}</span>
|
||||
<span
|
||||
style={{
|
||||
fontSize: '10px',
|
||||
padding: '0 4px',
|
||||
borderRadius: '8px',
|
||||
background: 'var(--bg-subtle)',
|
||||
color: 'var(--text-faint)',
|
||||
}}
|
||||
>
|
||||
{currentCatInfo.count}
|
||||
</span>
|
||||
<ChevronDown size={10} style={{ color: 'var(--text-muted)' }} />
|
||||
</button>
|
||||
|
||||
{/* 导出受控 Word 按钮 */}
|
||||
{showCategoryMenu && (
|
||||
<div
|
||||
className="dropdown-menu"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: '28px',
|
||||
width: '150px',
|
||||
background: 'var(--bg-panel)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: '6px',
|
||||
boxShadow: 'var(--shadow-lg)',
|
||||
zIndex: 100,
|
||||
padding: '4px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '2px',
|
||||
}}
|
||||
>
|
||||
{(['workflow', 'shared', 'private'] as DeliverableCategory[]).map((cat) => {
|
||||
const info = categoryConfig[cat];
|
||||
const isSelected = activeCategory === cat;
|
||||
return (
|
||||
<button
|
||||
key={cat}
|
||||
onClick={() => {
|
||||
handleCategoryClick(cat);
|
||||
setShowCategoryMenu(false);
|
||||
}}
|
||||
style={{
|
||||
height: '28px',
|
||||
padding: '0 8px',
|
||||
borderRadius: '4px',
|
||||
border: 'none',
|
||||
background: isSelected ? 'var(--bg-subtle)' : 'transparent',
|
||||
color: isSelected ? info.color : 'var(--text)',
|
||||
fontSize: '11.5px',
|
||||
fontWeight: isSelected ? 600 : 400,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
|
||||
{info.icon}
|
||||
<span>{info.label}</span>
|
||||
</div>
|
||||
<span style={{ fontSize: '10px', color: 'var(--text-faint)' }}>{info.count} 份</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 面包屑斜杠 */}
|
||||
<span style={{ color: 'var(--text-faint)', fontSize: '11px', flexShrink: 0 }}>/</span>
|
||||
|
||||
{/* 2. 当前文档级联下拉选择器 (占满中间自适应空间,带文本截断与状态点) */}
|
||||
<div style={{ position: 'relative', flex: 1, minWidth: 0 }}>
|
||||
<button
|
||||
className="dropdown-trigger"
|
||||
onClick={() => {
|
||||
setShowDocMenu(!showDocMenu);
|
||||
setShowCategoryMenu(false);
|
||||
}}
|
||||
style={{
|
||||
height: '24px',
|
||||
width: '100%',
|
||||
padding: '0 8px',
|
||||
borderRadius: '5px',
|
||||
border: '1px solid var(--border)',
|
||||
background: showDocMenu ? 'var(--bg-hover)' : 'var(--bg-panel)',
|
||||
color: 'var(--text-strong)',
|
||||
fontSize: '11.5px',
|
||||
fontWeight: 600,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '6px',
|
||||
cursor: 'pointer',
|
||||
textAlign: 'left',
|
||||
overflow: 'hidden',
|
||||
transition: 'background 0.15s ease',
|
||||
}}
|
||||
title={activeDoc ? `${activeDoc.title} (点击下拉快速切换文档)` : '请选择文档'}
|
||||
>
|
||||
{activeDoc?.category === 'private' ? (
|
||||
<Lock size={12} color="#D97706" style={{ flexShrink: 0 }} />
|
||||
) : activeDoc?.category === 'shared' ? (
|
||||
<Globe size={12} color="var(--brand-500)" style={{ flexShrink: 0 }} />
|
||||
) : docStatus ? (
|
||||
<div
|
||||
style={{
|
||||
width: '6px',
|
||||
height: '6px',
|
||||
borderRadius: '50%',
|
||||
background: docStatus.color,
|
||||
boxShadow: `0 0 4px ${docStatus.color}`,
|
||||
flexShrink: 0,
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<span
|
||||
style={{
|
||||
flex: 1,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{activeDoc ? activeDoc.title : '请选择文档'}
|
||||
</span>
|
||||
|
||||
<ChevronDown size={11} style={{ color: 'var(--text-muted)', flexShrink: 0 }} />
|
||||
</button>
|
||||
|
||||
{showDocMenu && (
|
||||
<div
|
||||
className="dropdown-menu"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: '28px',
|
||||
width: '100%',
|
||||
minWidth: '240px',
|
||||
maxWidth: '380px',
|
||||
maxHeight: '280px',
|
||||
overflowY: 'auto',
|
||||
background: 'var(--bg-panel)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: '6px',
|
||||
boxShadow: 'var(--shadow-lg)',
|
||||
zIndex: 100,
|
||||
padding: '4px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '2px',
|
||||
}}
|
||||
>
|
||||
<div style={{ padding: '4px 8px', fontSize: '10px', color: 'var(--text-faint)', fontWeight: 600 }}>
|
||||
{currentCatInfo.label}清单 ({visibleTabs.length} 份):
|
||||
</div>
|
||||
{visibleTabs.length === 0 ? (
|
||||
<div style={{ padding: '10px 8px', fontSize: '11px', color: 'var(--text-faint)', textAlign: 'center' }}>
|
||||
暂无文档
|
||||
</div>
|
||||
) : (
|
||||
visibleTabs.map((doc) => {
|
||||
const isCur = doc.docId === activeDocId;
|
||||
const st = getStatusDot(doc.lifecycleState);
|
||||
return (
|
||||
<button
|
||||
key={doc.docId}
|
||||
onClick={() => {
|
||||
onSelectDoc(doc.docId);
|
||||
setShowDocMenu(false);
|
||||
}}
|
||||
style={{
|
||||
padding: '6px 8px',
|
||||
borderRadius: '4px',
|
||||
border: 'none',
|
||||
background: isCur ? 'var(--bg-subtle)' : 'transparent',
|
||||
color: isCur ? 'var(--brand-500)' : 'var(--text)',
|
||||
fontSize: '11px',
|
||||
fontWeight: isCur ? 600 : 400,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '6px',
|
||||
cursor: 'pointer',
|
||||
textAlign: 'left',
|
||||
transition: 'background 0.12s ease',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: '6px',
|
||||
height: '6px',
|
||||
borderRadius: '50%',
|
||||
background: st.color,
|
||||
flexShrink: 0,
|
||||
}}
|
||||
/>
|
||||
<span style={{ flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
||||
{doc.title}
|
||||
</span>
|
||||
{isCur && <Check size={11} color="var(--brand-500)" style={{ flexShrink: 0 }} />}
|
||||
</button>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 3. 右侧全局紧凑操作区 */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '3px', flexShrink: 0 }}>
|
||||
{/* 全景大盘抽屉入口 (宽屏带文字,窄屏收缩为纯图标+数字) */}
|
||||
<button
|
||||
onClick={onToggleDeckDrawer}
|
||||
style={{
|
||||
height: '24px',
|
||||
padding: isCompact ? '0 6px' : '0 8px',
|
||||
borderRadius: '4px',
|
||||
border: '1px solid var(--border)',
|
||||
background: 'var(--bg-panel)',
|
||||
color: 'var(--text-strong)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '3px',
|
||||
cursor: 'pointer',
|
||||
fontSize: '11px',
|
||||
fontWeight: 600,
|
||||
transition: 'all 0.15s ease',
|
||||
}}
|
||||
title={`展开全景大盘 (当前共 ${deliverables.length} 份交付物)`}
|
||||
>
|
||||
<Layers size={12} color="var(--brand-500)" />
|
||||
{!isCompact && <span>全景大盘</span>}
|
||||
<span style={{ fontSize: '10px', color: 'var(--text-faint)' }}>({deliverables.length})</span>
|
||||
</button>
|
||||
|
||||
{/* 导出 DOCX 图标 */}
|
||||
{onExportDocx && (
|
||||
<button
|
||||
onClick={onExportDocx}
|
||||
style={{
|
||||
height: '26px',
|
||||
padding: '0 8px',
|
||||
borderRadius: 'var(--r-sm)',
|
||||
background: 'var(--brand-50)',
|
||||
border: '1px solid var(--brand-100)',
|
||||
color: 'var(--brand-600)',
|
||||
fontSize: '11px',
|
||||
fontWeight: 600,
|
||||
width: '24px',
|
||||
height: '24px',
|
||||
borderRadius: '4px',
|
||||
border: '1px solid var(--border)',
|
||||
background: 'var(--bg-panel)',
|
||||
color: 'var(--text-muted)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '4px',
|
||||
justifyContent: 'center',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.15s ease',
|
||||
}}
|
||||
title="导出为符合严肃合规模板的受控 DOCX 交付物"
|
||||
title="导出为符合合规模板的 DOCX 受控文件"
|
||||
>
|
||||
<FileDown size={13} />
|
||||
<span>导出 DOCX</span>
|
||||
<FileDown size={12} />
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* 全屏切换 */}
|
||||
{/* 全屏切换图标 */}
|
||||
{onToggleFullscreen && (
|
||||
<button
|
||||
onClick={onToggleFullscreen}
|
||||
style={{
|
||||
width: '26px',
|
||||
height: '26px',
|
||||
borderRadius: 'var(--r-sm)',
|
||||
border: 'none',
|
||||
background: 'transparent',
|
||||
width: '24px',
|
||||
height: '24px',
|
||||
borderRadius: '4px',
|
||||
border: '1px solid var(--border)',
|
||||
background: 'var(--bg-panel)',
|
||||
color: 'var(--text-muted)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
color: 'var(--text-muted)',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.15s ease',
|
||||
}}
|
||||
title={isFullscreen ? '退出全屏' : '全屏沉浸阅读与编辑'}
|
||||
title={isFullscreen ? '退出全屏' : '全屏沉浸阅读'}
|
||||
>
|
||||
{isFullscreen ? <Minimize2 size={13} /> : <Maximize2 size={13} />}
|
||||
{isFullscreen ? <Minimize2 size={12} /> : <Maximize2 size={12} />}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user