UpdateNotification.tsx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. import React, { useEffect, useState } from 'react';
  2. import { CloseOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
  3. import ReactMarkdown from 'react-markdown';
  4. import rehypeRaw from 'rehype-raw';
  5. import { shouldShowUpdate, isUpdateNotificationDisabled, getCurrentVersion, getVersionHistory } from './version';
  6. import ImagePreview from '../ImagePreview';
  7. import './style.less';
  8. interface UpdateNotificationProps {
  9. version: string; // 版本号,用于控制是否显示
  10. }
  11. const UpdateNotification: React.FC<UpdateNotificationProps> = ({ version }) => {
  12. const [visible, setVisible] = useState(false);
  13. const [content, setContent] = useState('');
  14. const [isAnimating, setIsAnimating] = useState(false);
  15. const [previewImage, setPreviewImage] = useState<string | null>(null);
  16. useEffect(() => {
  17. // 检查是否被禁用
  18. if (isUpdateNotificationDisabled()) {
  19. return;
  20. }
  21. // 检查是否已经看过这个版本的更新
  22. const lastViewedVersion = localStorage.getItem('lastViewedUpdateVersion');
  23. if (shouldShowUpdate(lastViewedVersion)) {
  24. // 立即保存版本号,防止退出登录或刷新后重复显示
  25. localStorage.setItem('lastViewedUpdateVersion', version);
  26. // 加载更新内容 - 从历史记录中提取最新版本
  27. import('../../docs/update-history/index.md?raw')
  28. .then((module) => {
  29. const fullContent = module.default;
  30. // 提取第一个版本的内容(从第一个 ## 📅 到下一个 --- 之间的内容)
  31. const versionRegex = /##\s+📅.*?\n\n([\s\S]*?)(?=\n---)/;
  32. const match = fullContent.match(versionRegex);
  33. if (match && match[1]) {
  34. // 只取内容部分,不包括版本标题
  35. setContent(match[1].trim());
  36. } else {
  37. // 如果匹配失败,显示整个文档
  38. console.warn('无法提取最新版本内容,显示完整文档');
  39. setContent(fullContent);
  40. }
  41. setTimeout(() => {
  42. setVisible(true);
  43. setIsAnimating(true);
  44. }, 800);
  45. })
  46. .catch((error) => {
  47. console.error('Failed to load update content:', error);
  48. });
  49. }
  50. }, [version]);
  51. const handleClose = () => {
  52. setIsAnimating(false);
  53. setTimeout(() => {
  54. setVisible(false);
  55. }, 300);
  56. };
  57. const handleImageClick = (src: string) => {
  58. setPreviewImage(src);
  59. };
  60. const handleClosePreview = () => {
  61. setPreviewImage(null);
  62. };
  63. if (!visible) return null;
  64. return (
  65. <>
  66. {/* 遮罩层 */}
  67. <div
  68. className={`update-notification-mask ${isAnimating ? 'show' : ''}`}
  69. onClick={handleClose}
  70. />
  71. {/* 弹窗 */}
  72. <div className={`update-notification ${isAnimating ? 'show' : ''}`}>
  73. <div className="update-notification-header">
  74. <div className="update-notification-title">
  75. <span>{getVersionHistory().find(v => v.version === getCurrentVersion())?.date || new Date().toISOString().split('T')[0]} 版本公告</span>
  76. </div>
  77. <CloseOutlined
  78. className="update-notification-close"
  79. onClick={handleClose}
  80. />
  81. </div>
  82. <div className="update-notification-content">
  83. <ReactMarkdown
  84. rehypePlugins={[rehypeRaw]}
  85. components={{
  86. h3: (props) => <h3 {...props} />,
  87. h4: (props: React.HTMLAttributes<HTMLHeadingElement> & { className?: string }) => (
  88. <h4 className={props.className} {...props} />
  89. ),
  90. p: (props) => <p {...props} />,
  91. div: (props: React.HTMLAttributes<HTMLDivElement> & { className?: string }) => {
  92. if (props.className === 'update-item') {
  93. return <div className="update-item" {...props} />;
  94. }
  95. return <div {...props} />;
  96. },
  97. img: (props: React.ImgHTMLAttributes<HTMLImageElement> & { src?: string; alt?: string }) => {
  98. const src = props.src?.startsWith('/') ? props.src : `/${props.src || ''}`;
  99. return (
  100. <img
  101. {...props}
  102. src={src}
  103. alt={props.alt || ''}
  104. style={{ cursor: 'pointer' }}
  105. onClick={() => handleImageClick(src)}
  106. />
  107. );
  108. },
  109. }}
  110. >
  111. {content}
  112. </ReactMarkdown>
  113. </div>
  114. <div className="update-notification-footer">
  115. <button className="update-notification-button" onClick={handleClose}>
  116. 关闭
  117. </button>
  118. <div
  119. className="footer-notice"
  120. onClick={() => window.open('/help/update-history', '_blank')}
  121. style={{ cursor: 'pointer' }}
  122. >
  123. <ExclamationCircleOutlined className="info-icon" />
  124. <span className="notice-text">版本公告将在帮助文档中</span>
  125. </div>
  126. </div>
  127. </div>
  128. {/* 图片预览模态框 */}
  129. <ImagePreview src={previewImage} onClose={handleClosePreview} />
  130. </>
  131. );
  132. };
  133. export default UpdateNotification;