/** * MainToolbar.tsx - 主工具栏组件 * * 编辑器顶部工具栏,包含保存、导出、关闭等操作 * * @module components/Editor/toolbar */ import React, { useState } from 'react'; import { Button, Dropdown, Divider, message, Modal } from 'antd'; import type { MenuProps } from 'antd'; import { useShallow } from 'zustand/react/shallow'; import { SaveOutlined, CloseOutlined, DownloadOutlined, FileWordOutlined, FilePdfOutlined, FileMarkdownOutlined, DownOutlined, LoadingOutlined, ExclamationCircleOutlined, UndoOutlined, RedoOutlined, } from '@ant-design/icons'; import { useEditorStore } from '../../../stores/editorStore'; import { exportToWord } from '../../../services/exportService'; import { downloadExportRecord } from '../../../services/exportRecordService'; import { exportBlocksToMarkdown, exportEditorToPdf } from '../../../services/clientExportService'; import './MainToolbar.css'; // ══════════════════════════════════════════════════════════════════════════════ // Component Props // ══════════════════════════════════════════════════════════════════════════════ export interface MainToolbarProps { /** 文档标题 */ documentTitle: string; /** 文档ID */ documentId: string; /** 是否只读 */ readOnly?: boolean; /** 保存回调 */ onSave?: () => void; /** 关闭回调 */ onClose?: () => void; } // ══════════════════════════════════════════════════════════════════════════════ // Component // ══════════════════════════════════════════════════════════════════════════════ /** * MainToolbar - 主工具栏 */ export const MainToolbar: React.FC = ({ documentTitle, documentId, readOnly = false, onSave, onClose, }) => { // ── State ────────────────────────────────────────────────────────────────── const [isExporting, setIsExporting] = useState(false); // ── Editor Store ─────────────────────────────────────────────────────────── // 获取文档修改状态和保存状态 const { hasModified, failedBlocks, isSaving, autoSaveEnabled, setAutoSaveEnabled, lastSaveTime, savingProgress, pastLength, futureLength, isHistoryApplying, pendingStructuralOperations, undo, redo, } = useEditorStore( useShallow((state) => ({ hasModified: state.hasModified, failedBlocks: state.failedBlocks, isSaving: state.isSaving, autoSaveEnabled: state.autoSaveEnabled, setAutoSaveEnabled: state.setAutoSaveEnabled, lastSaveTime: state.lastSaveTime, savingProgress: state.savingProgress, pastLength: state.past.length, futureLength: state.future.length, isHistoryApplying: state.isHistoryApplying, pendingStructuralOperations: state.pendingStructuralOperations, undo: state.undo, redo: state.redo, })) ); // ── 格式化上次保存时间 ──────────────────────────────────────────────────── const formatLastSaveTime = () => { if (!lastSaveTime) return ''; const time = new Date(lastSaveTime); return `${time.getHours()}:${time.getMinutes().toString().padStart(2, '0')} 保存`; }; // ── Export handlers ──────────────────────────────────────────────────────── /** * 处理关闭操作 * 如果有未保存的修改,弹出确认对话框 */ const handleClose = () => { if (hasModified || isSaving || failedBlocks.length > 0) { Modal.confirm({ title: isSaving ? '文档正在保存' : '未保存的修改', icon: , content: isSaving ? '保存请求尚未完成,关闭可能导致最新修改未写入服务器。确定要关闭吗?' : '您有未保存或保存失败的修改,确定要关闭吗?', okText: '关闭', okType: 'danger', cancelText: '取消', onOk: () => { onClose?.(); }, }); } else { onClose?.(); } }; /** * 导出为Word文档 */ const handleExportWord = async () => { if (!documentId) { message.error('无法导出:文档ID不存在'); return; } setIsExporting(true); const hideLoading = message.loading('正在导出Word文档...', 0); try { // 1. 调用导出API const response = await exportToWord({ documentId, styleId: null, // 使用默认样式 }); hideLoading(); // 2. 显示导出成功提示 if (response.warning) { message.warning(response.warning); } else { message.success('导出成功!'); } // 3. 触发下载 // 使用downloadExportRecord来触发浏览器下载 const userId = 'default-user'; // TODO: 从认证上下文获取 await downloadExportRecord(response.recordId, userId); } catch (error: unknown) { hideLoading(); message.error(error instanceof Error ? error.message : '导出Word文档失败'); } finally { setIsExporting(false); } }; const handleExportPDF = async () => { setIsExporting(true); const hideLoading = message.loading('正在生成PDF文档...', 0); try { await exportEditorToPdf(documentTitle); message.success('PDF导出成功'); } catch (error) { message.error(error instanceof Error ? error.message : 'PDF导出失败'); } finally { hideLoading(); setIsExporting(false); } }; const handleExportMarkdown = () => { exportBlocksToMarkdown(useEditorStore.getState().blocks, documentTitle); message.success('Markdown导出成功'); }; // 导出菜单 const exportMenuItems: MenuProps['items'] = [ { key: 'word', icon: , label: 'Word', disabled: isExporting, onClick: handleExportWord, }, { key: 'pdf', icon: , label: 'PDF', disabled: isExporting, onClick: handleExportPDF, }, { key: 'markdown', icon: , label: 'Markdown', disabled: isExporting, onClick: handleExportMarkdown, }, ]; return (
{/* 左侧 - 标题 */}
{documentTitle || '未命名文档'} {/* 修改状态指示 */} {hasModified && ( 未保存 )} {/* 保存状态显示 */} {isSaving && savingProgress && ( 保存中 ({savingProgress.current}/{savingProgress.total}) )} {/* 自动保存时间显示 */} {!hasModified && !isSaving && lastSaveTime && ( {formatLastSaveTime()} )}
{/* 右侧 - 操作按钮 */}
{!readOnly && ( <> {/* 保存 */} )} {/* 导出 */} {/* 关闭 */}
); }; export default MainToolbar;