import { useState, useEffect, useRef } from 'react' import { useNavigate, useParams } from 'react-router-dom' import { Layout, Modal, Input, Spin, Button, Space } from 'antd' import { CloseOutlined, LockOutlined, FileTextOutlined, FilePdfOutlined, VerticalAlignTopOutlined, CloudDownloadOutlined } from '@ant-design/icons' import ReactMarkdown from 'react-markdown' import remarkGfm from 'remark-gfm' import rehypeHighlight from 'rehype-highlight' import rehypeSlug from 'rehype-slug' import 'highlight.js/styles/github.css' import GithubSlugger from 'github-slugger' import Toast from '@/components/Toast/Toast' import FloatingToc from '@/components/FloatingToc/FloatingToc' import VirtualPDFViewer from '@/components/PDFViewer/VirtualPDFViewer' import { getFileSharePublicInfo, verifyFileSharePassword, getFileShareContent, exportFileSharePDF, } from '@/api/share' import './PreviewPage.css' const { Content } = Layout function FileSharePage() { const { shareCode } = useParams() const navigate = useNavigate() const contentRef = useRef(null) const [pdfToolbarTarget, setPdfToolbarTarget] = useState(null) const [shareInfo, setShareInfo] = useState(null) const [contentInfo, setContentInfo] = useState(null) const [loading, setLoading] = useState(true) const [isMobile, setIsMobile] = useState(false) const [tocItems, setTocItems] = useState([]) const [passwordModalVisible, setPasswordModalVisible] = useState(false) const [password, setPassword] = useState('') useEffect(() => { loadFileShare() }, [shareCode]) useEffect(() => { const checkMobile = () => setIsMobile(window.innerWidth < 768) checkMobile() window.addEventListener('resize', checkMobile) return () => window.removeEventListener('resize', checkMobile) }, []) useEffect(() => { const markdownContent = contentInfo?.type === 'markdown' ? (contentInfo.content || '') : '' if (!markdownContent) { setTocItems([]) return } const slugger = new GithubSlugger() const headings = [] markdownContent.split('\n').forEach((line) => { const match = line.match(/^(#{1,6})\s+(.+)$/) if (!match) return const level = match[1].length const title = match[2].trim() const key = slugger.slug(title) headings.push({ key: `#${key}`, href: `#${key}`, title, level }) }) setTocItems(headings) }, [contentInfo]) const handleClose = () => { if (window.history.length > 1) { navigate(-1) return } navigate('/') } const loadFileShare = async () => { setLoading(true) try { const infoRes = await getFileSharePublicInfo(shareCode) setShareInfo(infoRes.data) if (infoRes.data.has_password) { setContentInfo(null) setPasswordModalVisible(true) setLoading(false) return } const contentRes = await getFileShareContent(shareCode) setContentInfo(contentRes.data) } catch (error) { console.error('Load file share error:', error) Toast.error('加载失败', '分享链接不存在或已失效') } finally { setLoading(false) } } const handleVerifyPassword = async () => { if (!password.trim()) { Toast.warning('提示', '请输入访问密码') return } try { await verifyFileSharePassword(shareCode, password) const contentRes = await getFileShareContent(shareCode, password) setContentInfo(contentRes.data) setPasswordModalVisible(false) Toast.success('验证成功') } catch (error) { Toast.error('访问密码错误') } } const handleExportPDF = () => { if (!contentInfo || contentInfo.type === 'pdf') return window.open(exportFileSharePDF(shareCode), '_blank') } const scrollContentToTop = () => { if (contentRef.current) { contentRef.current.scrollTo({ top: 0, behavior: 'smooth' }) } } const isExternalHref = (href) => { return Boolean(href && (/^[a-z][a-z\d+.-]*:/i.test(href) || href.startsWith('//'))) } const isInternalFileHref = (href) => { if (!href) return false if (href.startsWith('#')) return false if (isExternalHref(href)) return false const pathOnly = href.split(/[?#]/)[0] return pathOnly.endsWith('.md') || pathOnly.toLowerCase().endsWith('.pdf') } const handleMarkdownLink = (e, href) => { if (!isInternalFileHref(href)) return e.preventDefault() Toast.error('无法打开内部文件链接', '单文件分享模式不支持跳转到其他内部文件') } const markdownComponents = { a: ({ node, href, children, ...props }) => { const isExternal = isExternalHref(href) return ( handleMarkdownLink(e, href)} target={isExternal ? '_blank' : undefined} rel={isExternal ? 'noopener noreferrer' : undefined} {...props} > {children} ) }, } const isHeaderPdf = contentInfo?.type === 'pdf' const HeaderIcon = isHeaderPdf ? FilePdfOutlined : FileTextOutlined const headerLabel = contentInfo?.filename || '文件分享' return (

{headerLabel}

{contentInfo?.type === 'markdown' && ( )} {contentInfo?.type === 'pdf' &&
}
{loading ? (
加载中...
) : (
{contentInfo?.type === 'pdf' ? ( ) : (
{contentInfo?.content || ''}
)}
)} {!isMobile && contentInfo?.type === 'markdown' && ( contentRef.current} /> )}
访问验证
} open={passwordModalVisible} onOk={handleVerifyPassword} onCancel={() => setPasswordModalVisible(false)} okText="验证" cancelText="取消" maskClosable={false} >

该文件分享需要访问密码,请输入密码后继续浏览。

setPassword(e.target.value)} onPressEnter={handleVerifyPassword} prefix={} />
) } export default FileSharePage