Forráskód Böngészése

Merge branch 'dev/permission' of https://git.zyuas.com/LLM/chat-admin-web into dev/permission

sunsheng 1 hónapja
szülő
commit
f09664e5c7

BIN
public/banbenimg/bb1 (1).png


BIN
public/banbenimg/bb1 (2).png


BIN
public/banbenimg/bb1 (3).png


BIN
public/kg4.png


+ 13 - 1
src/LocalStorage.ts

@@ -83,13 +83,25 @@ class LocalStorage {
     // 清除
     clear = () => {
         /*
-         * 保留密码
+         * 保留密码和更新通知记录
          */
         const accountPassword = this.getAccountPassword();
         const cloneAccountPassword = accountPassword ? { ...accountPassword } : undefined;
+        
+        // 保存更新通知相关数据
+        const lastViewedVersion = localStorage.getItem('lastViewedUpdateVersion');
+        const disableUpdateNotification = localStorage.getItem('disableUpdateNotification');
 
         localStorage.clear();
+        
+        // 恢复保留的数据
         this.setAccountPassword(cloneAccountPassword);
+        if (lastViewedVersion) {
+            localStorage.setItem('lastViewedUpdateVersion', lastViewedVersion);
+        }
+        if (disableUpdateNotification) {
+            localStorage.setItem('disableUpdateNotification', disableUpdateNotification);
+        }
     }
 
     // 存储roles

+ 4 - 5
src/help/components/HelpLayout.tsx

@@ -6,7 +6,7 @@ import MarkdownViewer, { TocItem } from './MarkdownViewer';
 
 const { Sider, Content } = Layout;
 
-// 动态加载 md 文件(Vite 6 写法)
+// 动态加载 md 文件
 const files = import.meta.glob('/src/help/docs/**/*.md', { query: '?raw', import: 'default' });
 
 function findMenuByPath(pathname: string): HelpMenuItem | undefined {
@@ -59,12 +59,11 @@ const HelpLayout: React.FC = () => {
     }
   }, [md, location.hash]);
 
-  // build antd menu items
   const menuItems = useMemo(() => {
-    const toAntd = (items: HelpMenuItem[]): any[] => items.map((it) => ({
+    const toAntd = (items: HelpMenuItem[], isChild = false): any[] => items.map((it) => ({
       key: it.path, // 使用完整路径作为 key
-      label: it.title,
-      children: it.children ? toAntd(it.children) : undefined,
+      label: <span style={isChild ? { paddingLeft: '16px' } : undefined}>{it.title}</span>,
+      children: it.children ? toAntd(it.children, true) : undefined,
     }));
     return toAntd(helpMenu);
   }, []);

+ 100 - 0
src/help/components/ImagePreview.less

@@ -0,0 +1,100 @@
+/* 图片预览模态框 */
+.image-preview-mask {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.75);
+  z-index: 10000;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  animation: fadeIn 0.3s ease;
+}
+
+.image-preview-container {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  z-index: 10001;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 40px;
+}
+
+.image-preview {
+  max-width: 90vw;
+  max-height: 90vh;
+  object-fit: contain;
+  border-radius: 4px;
+  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
+  animation: zoomIn 0.3s ease;
+}
+
+.image-preview-close {
+  position: absolute;
+  top: 20px;
+  right: 20px;
+  font-size: 24px;
+  color: #fff;
+  cursor: pointer;
+  background: rgba(0, 0, 0, 0.5);
+  width: 40px;
+  height: 40px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  border-radius: 50%;
+  transition: all 0.2s ease;
+  z-index: 10002;
+
+  &:hover {
+    background: rgba(0, 0, 0, 0.7);
+    transform: scale(1.1);
+  }
+}
+
+@keyframes fadeIn {
+  from {
+    opacity: 0;
+  }
+  to {
+    opacity: 1;
+  }
+}
+
+@keyframes zoomIn {
+  from {
+    opacity: 0;
+    transform: scale(0.9);
+  }
+  to {
+    opacity: 1;
+    transform: scale(1);
+  }
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+  .image-preview-container {
+    padding: 20px;
+  }
+
+  .image-preview {
+    max-width: 95vw;
+    max-height: 85vh;
+  }
+
+  .image-preview-close {
+    top: 10px;
+    right: 10px;
+    width: 36px;
+    height: 36px;
+    font-size: 20px;
+  }
+}
+

+ 36 - 0
src/help/components/ImagePreview.tsx

@@ -0,0 +1,36 @@
+import React from 'react';
+import { CloseOutlined } from '@ant-design/icons';
+import './ImagePreview.less';
+
+interface ImagePreviewProps {
+  src: string | null;
+  onClose: () => void;
+}
+
+const ImagePreview: React.FC<ImagePreviewProps> = ({ src, onClose }) => {
+  if (!src) return null;
+
+  return (
+    <>
+      <div 
+        className="image-preview-mask"
+        onClick={onClose}
+      />
+      <div className="image-preview-container" onClick={onClose}>
+        <img 
+          src={src} 
+          alt="预览" 
+          className="image-preview"
+          onClick={(e) => e.stopPropagation()}
+        />
+        <CloseOutlined 
+          className="image-preview-close" 
+          onClick={onClose}
+        />
+      </div>
+    </>
+  );
+};
+
+export default ImagePreview;
+

+ 161 - 8
src/help/components/MarkdownViewer.tsx

@@ -1,8 +1,9 @@
-import React, { useEffect, useMemo, useRef, useState } from 'react';
+import React, { useEffect, useMemo, useRef, useState, useCallback } from 'react';
 import { useNavigate } from 'react-router-dom';
 import { BulbOutlined, InfoCircleOutlined, CheckCircleOutlined, ExclamationCircleOutlined, CloseCircleOutlined } from '@ant-design/icons';
 import ReactMarkdown from 'react-markdown';
 import rehypeRaw from 'rehype-raw';
+import ImagePreview from './ImagePreview';
 
 export type TocItem = { id: string; text: string; level: number };
 
@@ -21,9 +22,18 @@ interface MarkdownViewerProps {
 
 const MarkdownViewer: React.FC<MarkdownViewerProps> = ({ content, onToc }) => {
   const [toc, setToc] = useState<TocItem[]>([]);
+  const [previewImage, setPreviewImage] = useState<string | null>(null);
   const containerRef = useRef<HTMLDivElement | null>(null);
   const navigate = useNavigate();
 
+  const handleImageClick = useCallback((src: string) => {
+    setPreviewImage(src);
+  }, []);
+
+  const handleClosePreview = useCallback(() => {
+    setPreviewImage(null);
+  }, []);
+
   const components = useMemo(() => {
     const getNodeText = (node: any): string => {
       if (node == null) return '';
@@ -90,12 +100,17 @@ const MarkdownViewer: React.FC<MarkdownViewerProps> = ({ content, onToc }) => {
           />
         );
       },
-      img: (props: any) => (
-        <img
-          style={{ maxWidth: '100%', height: 'auto', display: 'block', margin: '12px auto' }}
-          {...props}
-        />
-      ),
+      img: (props: any) => {
+        const src = props.src?.startsWith('/') ? props.src : `/${props.src || ''}`;
+        return (
+          <img
+            style={{ maxWidth: '100%', height: 'auto', display: 'block', margin: '12px auto', cursor: 'pointer' }}
+            {...props}
+            src={src}
+            onClick={() => handleImageClick(src)}
+          />
+        );
+      },
       // Custom: <Tip type="info|warn|success">...children...</Tip>
       tip: (props: any) => {
         const type = props?.node?.properties?.type || 'info';
@@ -206,7 +221,7 @@ const MarkdownViewer: React.FC<MarkdownViewerProps> = ({ content, onToc }) => {
         );
       },
     } as any;
-  }, []);
+  }, [handleImageClick, navigate]);
 
   useEffect(() => {
     // build toc after render
@@ -241,10 +256,148 @@ const MarkdownViewer: React.FC<MarkdownViewerProps> = ({ content, onToc }) => {
         .help-tip-success{border-radius:14px;padding:16px 18px;border-color:#d1fadf;background:#f6fef9;box-shadow:0 2px 8px rgba(4,108,78,0.04) inset, 0 1px 2px rgba(0,0,0,0.02)}
         .help-tip-success a{color:#0f766e}
         .help-tip-success img{border-radius:12px;box-shadow:0 10px 24px rgba(2,44,34,0.08);}
+        /* Update item style - same as UpdateNotification */
+        .help-markdown .update-item{
+          display:flex;
+          align-items:flex-start;
+          gap:0;
+          padding:0;
+          background:transparent;
+          border-radius:0;
+          border:none;
+          transition:all 0.3s ease;
+          position:relative;
+          height:120px;
+          overflow:hidden;
+        }
+        .help-markdown .update-item:first-of-type{margin-top:0;}
+        .help-markdown .update-item:last-of-type{margin-bottom:0;}
+        .help-markdown .update-item-image{
+          flex-shrink:0;
+          width:210px;
+          min-width:180px;
+          height:120px;
+          display:flex;
+          align-items:center;
+          justify-content:center;
+          background:transparent;
+          border-radius:0;
+          overflow:hidden;
+          padding:0 0 0 30px;
+          position:relative;
+          margin-right:0;
+        }
+        .help-markdown .update-item-image::after{
+          content:'';
+          position:absolute;
+          right:0;
+          top:50%;
+          transform:translateY(-50%);
+          width:1px;
+          height:80px;
+          background:linear-gradient(to bottom, rgba(232,232,232,0) 0%, rgba(217,217,217,0.4) 15%, #d9d9d9 50%, rgba(217,217,217,0.4) 85%, rgba(232,232,232,0) 100%);
+        }
+        .help-markdown .update-item-image img{
+          width:100%;
+          height:100%;
+          object-fit:contain;
+          margin:0;
+          border-radius:0;
+          padding:0;
+          transition:transform 0.3s ease;
+          cursor:pointer;
+        }
+        .help-markdown .update-item-image img:hover{
+          transform:scale(1.02);
+        }
+        .help-markdown .update-item-content{
+          flex:1;
+          min-width:0;
+          height:120px;
+          padding-left:32px;
+          padding-top:0;
+          padding-right:10px;
+          display:flex;
+          flex-direction:column;
+          justify-content:flex-start;
+          overflow-y:auto;
+          overflow-x:hidden;
+        }
+        .help-markdown .update-item-content::-webkit-scrollbar{
+          width:4px;
+        }
+        .help-markdown .update-item-content::-webkit-scrollbar-track{
+          background:transparent;
+        }
+        .help-markdown .update-item-content::-webkit-scrollbar-thumb{
+          background:#d9d9d9;
+          border-radius:2px;
+          transition:background 0.2s;
+        }
+        .help-markdown .update-item-content::-webkit-scrollbar-thumb:hover{
+          background:#bfbfbf;
+        }
+        .help-markdown .update-item-content h3{
+          margin:0;
+          padding:0;
+          font-size:16px;
+          font-weight:600;
+          color:#262626;
+          line-height:1.4;
+          letter-spacing:0;
+        }
+        .help-markdown .update-item-content h3.blue-title{
+          color:#1677ff;
+        }
+        .help-markdown .update-item-content h3 a{
+          color:#1677ff;
+          font-size:16px;
+          font-weight:600;
+          font-style:italic;
+          text-decoration:underline;
+          transition:all 0.2s ease;
+        }
+        .help-markdown .update-item-content h3 a:hover{
+          color:#1890ff;
+        }
+        .help-markdown .update-item-content p{
+          margin:10px 0 0 0;
+          font-size:14px;
+          line-height:1.8;
+          color:#595959;
+        }
+        .help-markdown .update-item-content p:first-of-type{
+          margin-top:12px;
+        }
+        .help-markdown .update-item-content p + p{
+          margin-top:8px;
+        }
+        .help-markdown .update-item-content ul{
+          margin:12px 0 0 0;
+          padding-left:0;
+          list-style:none;
+        }
+        .help-markdown .update-item-content ul li{
+          color:rgba(0,0,0,0.65);
+          font-size:14px;
+          line-height:1.5;
+          padding-left:16px;
+          position:relative;
+        }
+        .help-markdown .update-item-content ul li::before{
+          content:'•';
+          position:absolute;
+          left:0;
+          color:rgba(0,0,0,0.65);
+          font-size:14px;
+        }
       `}</style>
       <ReactMarkdown rehypePlugins={[rehypeRaw]} components={components}>
         {content}
       </ReactMarkdown>
+
+      {/* 图片预览模态框 */}
+      <ImagePreview src={previewImage} onClose={handleClosePreview} />
     </div>
   );
 };

+ 149 - 0
src/help/components/UpdateNotification/UpdateNotification.tsx

@@ -0,0 +1,149 @@
+import React, { useEffect, useState } from 'react';
+import { CloseOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
+import ReactMarkdown from 'react-markdown';
+import rehypeRaw from 'rehype-raw';
+import { shouldShowUpdate, isUpdateNotificationDisabled, getCurrentVersion, getVersionHistory } from './version';
+import ImagePreview from '../ImagePreview';
+import './style.less';
+
+interface UpdateNotificationProps {
+  version: string; // 版本号,用于控制是否显示
+}
+
+const UpdateNotification: React.FC<UpdateNotificationProps> = ({ version }) => {
+  const [visible, setVisible] = useState(false);
+  const [content, setContent] = useState('');
+  const [isAnimating, setIsAnimating] = useState(false);
+  const [previewImage, setPreviewImage] = useState<string | null>(null);
+
+  useEffect(() => {
+    // 检查是否被禁用
+    if (isUpdateNotificationDisabled()) {
+      return;
+    }
+
+    // 检查是否已经看过这个版本的更新
+    const lastViewedVersion = localStorage.getItem('lastViewedUpdateVersion');
+    
+    if (shouldShowUpdate(lastViewedVersion)) {
+      // 立即保存版本号,防止退出登录或刷新后重复显示
+      localStorage.setItem('lastViewedUpdateVersion', version);
+      
+      // 加载更新内容 - 从历史记录中提取最新版本
+      import('../../docs/update-history/index.md?raw')
+        .then((module) => {
+          const fullContent = module.default;
+          // 提取第一个版本的内容(从第一个 ## 📅 到下一个 --- 之间的内容)
+          const versionRegex = /##\s+📅.*?\n\n([\s\S]*?)(?=\n---)/;
+          const match = fullContent.match(versionRegex);
+          
+          if (match && match[1]) {
+            // 只取内容部分,不包括版本标题
+            setContent(match[1].trim());
+          } else {
+            // 如果匹配失败,显示整个文档
+            console.warn('无法提取最新版本内容,显示完整文档');
+            setContent(fullContent);
+          }
+          
+          setTimeout(() => {
+            setVisible(true);
+            setIsAnimating(true);
+          }, 800);
+        })
+        .catch((error) => {
+          console.error('Failed to load update content:', error);
+        });
+    }
+  }, [version]);
+
+  const handleClose = () => {
+    setIsAnimating(false);
+    setTimeout(() => {
+      setVisible(false);
+    }, 300);
+  };
+
+  const handleImageClick = (src: string) => {
+    setPreviewImage(src);
+  };
+
+  const handleClosePreview = () => {
+    setPreviewImage(null);
+  };
+
+  if (!visible) return null;
+
+  return (
+    <>
+      {/* 遮罩层 */}
+      <div 
+        className={`update-notification-mask ${isAnimating ? 'show' : ''}`}
+        onClick={handleClose}
+      />
+      
+      {/* 弹窗 */}
+      <div className={`update-notification ${isAnimating ? 'show' : ''}`}>
+        <div className="update-notification-header">
+          <div className="update-notification-title">
+            <span>{getVersionHistory().find(v => v.version === getCurrentVersion())?.date || new Date().toISOString().split('T')[0]} 版本公告</span>
+          </div>
+          <CloseOutlined 
+            className="update-notification-close" 
+            onClick={handleClose}
+          />
+        </div>
+        <div className="update-notification-content">
+          <ReactMarkdown 
+            rehypePlugins={[rehypeRaw]}
+            components={{
+              h3: (props) => <h3 {...props} />,
+              h4: (props: React.HTMLAttributes<HTMLHeadingElement> & { className?: string }) => (
+                <h4 className={props.className} {...props} />
+              ),
+              p: (props) => <p {...props} />,
+              div: (props: React.HTMLAttributes<HTMLDivElement> & { className?: string }) => {
+                if (props.className === 'update-item') {
+                  return <div className="update-item" {...props} />;
+                }
+                return <div {...props} />;
+              },
+              img: (props: React.ImgHTMLAttributes<HTMLImageElement> & { src?: string; alt?: string }) => {
+                const src = props.src?.startsWith('/') ? props.src : `/${props.src || ''}`;
+                return (
+                  <img 
+                    {...props} 
+                    src={src} 
+                    alt={props.alt || ''} 
+                    style={{ cursor: 'pointer' }}
+                    onClick={() => handleImageClick(src)}
+                  />
+                );
+              },
+            }}
+          >
+            {content}
+          </ReactMarkdown>
+        </div>
+        <div className="update-notification-footer">
+          <button className="update-notification-button" onClick={handleClose}>
+            关闭
+          </button>
+          <div 
+            className="footer-notice" 
+            onClick={() => window.open('/help/update-history', '_blank')}
+            style={{ cursor: 'pointer' }}
+          >
+            <ExclamationCircleOutlined className="info-icon" />
+            <span className="notice-text">版本公告将在帮助文档中</span>
+          </div>
+        </div>
+      </div>
+
+      {/* 图片预览模态框 */}
+      <ImagePreview src={previewImage} onClose={handleClosePreview} />
+    </>
+  );
+};
+
+export default UpdateNotification;

+ 8 - 0
src/help/components/UpdateNotification/index.ts

@@ -0,0 +1,8 @@
+/**
+ * 更新通知组件
+ * 用于在右下角显示版本更新内容
+ */
+
+export { default } from './UpdateNotification';
+export * from './version';
+

+ 403 - 0
src/help/components/UpdateNotification/style.less

@@ -0,0 +1,403 @@
+@primary-color: #2152d1;
+@text-color: #303133;
+
+// 遮罩层
+.update-notification-mask {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.45);
+  z-index: 9998;
+  opacity: 0;
+  transition: opacity 0.3s ease;
+
+  &.show {
+    opacity: 1;
+  }
+}
+
+.update-notification {
+  position: fixed;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%) scale(0.8);
+  width: 980px;
+  max-width: 90vw;
+  max-height: 85vh;
+  background: #ffffff;
+  border-radius: 0;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+  z-index: 9999;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  opacity: 0;
+  transition: all 0.3s cubic-bezier(0.23, 1, 0.32, 1);
+
+  &.show {
+    opacity: 1;
+    transform: translate(-50%, -50%) scale(1);
+  }
+
+  &-header {
+    padding: 20px 32px;
+    border-bottom: none;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    position: relative;
+   
+  }
+
+  &-title {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    font-size: 20px;
+    font-weight: 600;
+    flex: 1;
+  }
+
+  &-icon {
+    font-size: 20px;
+    color: @primary-color;
+  }
+
+  &-close {
+    font-size: 16px;
+    color: #8c8c8c;
+    cursor: pointer;
+    padding: 4px 8px;
+    border-radius: 4px;
+    transition: all 0.2s ease;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin-left: 16px;
+
+    &:hover {
+      color: @text-color;
+      background: rgba(0, 0, 0, 0.06);
+    }
+
+    &:active {
+      transform: scale(0.95);
+    }
+  }
+
+  &-content {
+    flex: 1;
+    padding: 24px 40px;
+    overflow-y: auto;
+    overflow-x: hidden;
+    font-size: 14px;
+    line-height: 1.8;
+    color: @text-color;
+    min-height: 0;
+
+    /* 自定义滚动条样式 */
+    &::-webkit-scrollbar {
+      width: 6px;
+    }
+
+    &::-webkit-scrollbar-track {
+      background: #f5f5f5;
+    }
+
+    &::-webkit-scrollbar-thumb {
+      background: #d9d9d9;
+      border-radius: 3px;
+      transition: background 0.2s;
+
+      &:hover {
+        background: #bfbfbf;
+      }
+    }
+
+    /* Markdown 样式 */
+    h1, h2, h3, h4, h5, h6 {
+      color: @text-color;
+      font-weight: 600;
+      margin-top: 0;
+    }
+
+    h3 {
+      font-size: 16px;
+      margin: 0;
+      padding: 10px 0;
+      color: #262626;
+      font-weight: 600;
+    }
+
+    h4 {
+      font-size: 16px;
+      margin: 0;
+      color: #262626;
+      font-weight: 600;
+      line-height: 1.4;
+
+      &.blue-title {
+        color: #1677ff;
+      }
+    }
+
+    p {
+      margin: 0 0 8px 0;
+      font-size: 14px;
+      line-height: 1.8;
+      color: #595959;
+
+      &:last-child {
+        margin-bottom: 0;
+      }
+    }
+
+    /* 更新项左右布局样式 */
+    .update-item {
+      display: flex;
+      align-items: flex-start;
+      gap: 0;
+      padding: 0;
+      background: transparent;
+      border-radius: 0;
+      border: none;
+      transition: all 0.3s ease;
+      position: relative;
+      height: 120px;
+      overflow: hidden;
+
+      &:first-of-type {
+        margin-top: 0;
+      }
+
+      &:last-of-type {
+        margin-bottom: 0;
+      }
+
+      &:hover {
+        background: transparent;
+      }
+
+      &-image {
+        flex-shrink: 0;
+        width: 210px;
+        min-width: 180px;
+        height: 120px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        background: transparent;
+        border-radius: 0;
+        overflow: hidden;
+        padding: 0 0 0 30px;
+        position: relative;
+        margin-right: 0;
+
+        &::after {
+          content: '';
+          position: absolute;
+          right: 0;
+          top: 50%;
+          transform: translateY(-50%);
+          width: 1px;
+          height: 80px;
+          background: linear-gradient(to bottom, 
+            rgba(232, 232, 232, 0) 0%, 
+            rgba(217, 217, 217, 0.4) 15%, 
+            #d9d9d9 50%,
+            rgba(217, 217, 217, 0.4) 85%, 
+            rgba(232, 232, 232, 0) 100%);
+        }
+
+        img {
+          width: 100%;
+          height: 100%;
+          object-fit: contain;
+          margin: 0;
+          border-radius: 0;
+          padding: 0;
+          transition: transform 0.3s ease;
+          cursor: pointer;
+
+          &:hover {
+            transform: scale(1.02);
+          }
+        }
+      }
+
+      &-content {
+        flex: 1;
+        min-width: 0;
+        height: 120px;
+        padding-left: 32px;
+        padding-top: 0;
+        padding-right: 10px;
+        display: flex;
+        flex-direction: column;
+        justify-content: flex-start;
+        overflow-y: auto;
+        overflow-x: hidden;
+
+        /* 自定义滚动条样式 */
+        &::-webkit-scrollbar {
+          width: 4px;
+        }
+
+        &::-webkit-scrollbar-track {
+          background: transparent;
+        }
+
+        &::-webkit-scrollbar-thumb {
+          background: #d9d9d9;
+          border-radius: 2px;
+          transition: background 0.2s;
+
+          &:hover {
+            background: #bfbfbf;
+          }
+        }
+
+        h3 {
+          margin: 0;
+          padding: 0;
+          font-size: 16px;
+          font-weight: 600;
+          color: #262626;
+          line-height: 1.4;
+          letter-spacing: 0;
+
+          a {
+            color: #1677ff;
+            font-size: 16px;
+            font-weight: 600;
+            font-style: italic;
+            text-decoration: underline;
+            transition: all 0.2s ease;
+
+            &:hover {
+              color: #1890ff;
+            }
+          }
+        }
+
+        h4 {
+          margin-top: 0;
+          margin-bottom: 0;
+          font-size: 16px;
+          font-weight: 600;
+          color: #262626;
+          line-height: 1.4;
+          letter-spacing: 0;
+        }
+
+        p {
+          margin: 10px 0 0 0;
+          font-size: 14px;
+          line-height: 1.8;
+          color: #595959;
+
+          &:first-of-type {
+            margin-top: 12px;
+          }
+
+          & + p {
+            margin-top: 8px;
+          }
+        }
+
+        ul {
+          margin: 12px 0 0 0;
+          padding-left: 0;
+          list-style: none;
+
+          li {
+            color: rgba(0, 0, 0, 0.65);
+            font-size: 14px;
+            line-height: 1.5;
+            padding-left: 16px;
+            position: relative;
+
+            &::before {
+              content: '•';
+              position: absolute;
+              left: 0;
+              color: rgba(0, 0, 0, 0.65);
+              font-size: 14px;
+            }
+
+          }
+        }
+      }
+    }
+  }
+
+  &-footer {
+    padding: 16px 32px;
+    border-top: 1px solid #f0f0f0;
+    display: flex;
+    flex-direction: row-reverse;
+    justify-content: flex-start;
+    align-items: center;
+    background: #fff;
+    gap: 16px;
+
+    .footer-notice {
+      display: flex;
+      align-items: center;
+      gap: 0;
+      transition: all 0.2s ease;
+
+      &:hover {
+        opacity: 0.8;
+        
+        .info-icon {
+          color: #1677ff;
+        }
+        
+        .notice-text {
+          color: #3b3b3b;
+        }
+      }
+
+      .info-icon {
+        font-size: 14px;
+        color: #595959;
+        display: inline-flex;
+        align-items: center;
+        transition: color 0.2s ease;
+        font-weight: 700;
+      }
+
+      .notice-text {
+        font-size: 14px;
+        color: #929292;
+        transition: color 0.2s ease;
+        font-weight: 600;
+      }
+    }
+  }
+
+  &-button {
+    min-width: 88px;
+    padding: 8px 32px;
+    background: @primary-color;
+    color: #ffffff;
+    border: none;
+    border-radius: 4px;
+    font-size: 14px;
+    cursor: pointer;
+    transition: all 0.2s ease;
+    font-weight: 400;
+
+    &:hover {
+      background: #1a42a8;
+    }
+
+    &:active {
+      transform: scale(0.98);
+    }
+  }
+}
+

+ 94 - 0
src/help/components/UpdateNotification/version.ts

@@ -0,0 +1,94 @@
+/**
+ * 版本管理工具
+ * 用于管理更新通知的版本号
+ */
+
+// 当前版本号
+export const CURRENT_VERSION = '1.2.0';
+
+// 版本历史记录
+export const VERSION_HISTORY = [
+  {
+    version: '1.2.0',
+    date: '2025-12-05',
+    description: '应用广场优化、知识库增强、智能问答改进',
+  },
+  {
+    version: '1.1.0',
+    date: '2025-11-04',
+    description: '应用广场优化、知识库增强、智能问答改进',
+  },
+];
+
+/**
+ * 获取当前版本号
+ */
+export const getCurrentVersion = (): string => {
+  return CURRENT_VERSION;
+};
+
+/**
+ * 比较版本号
+ * @param v1 
+ * @param v2 
+ * @returns 
+ */
+export const compareVersions = (v1: string, v2: string): number => {
+  const parts1 = v1.split('.').map(Number);
+  const parts2 = v2.split('.').map(Number);
+  
+  for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
+    const num1 = parts1[i] || 0;
+    const num2 = parts2[i] || 0;
+    
+    if (num1 > num2) return 1;
+    if (num1 < num2) return -1;
+  }
+  
+  return 0;
+};
+
+/**
+ * 
+ * @param lastViewedVersion 
+ * @returns true: 需要显示, false: 不需要显示
+ */
+export const shouldShowUpdate = (lastViewedVersion: string | null): boolean => {
+  if (!lastViewedVersion) return true;
+  return compareVersions(CURRENT_VERSION, lastViewedVersion) > 0;
+};
+
+/**
+ * 获取版本历史
+ */
+export const getVersionHistory = () => {
+  return VERSION_HISTORY;
+};
+
+/**
+ * 重置查看记录
+ */
+export const resetViewedVersion = (): void => {
+  localStorage.removeItem('lastViewedUpdateVersion');
+};
+
+/**
+ * 强制禁用更新通知
+ */
+export const disableUpdateNotification = (): void => {
+  localStorage.setItem('disableUpdateNotification', 'true');
+};
+
+/**
+ * 启用更新通知
+ */
+export const enableUpdateNotification = (): void => {
+  localStorage.removeItem('disableUpdateNotification');
+};
+
+/**
+ * 检查更新通知是否被禁用
+ */
+export const isUpdateNotificationDisabled = (): boolean => {
+  return localStorage.getItem('disableUpdateNotification') === 'true';
+};

+ 107 - 0
src/help/docs/update-history/index.md

@@ -0,0 +1,107 @@
+# 更新历史
+
+这里记录了系统的所有版本更新信息,包括新功能、优化改进和问题修复。
+
+## 📅 2025-12-05 版本公告 (v1.2.0)
+
+### 【界面优化】
+
+<div class="update-item">
+  <div class="update-item-image">
+    <img src="/banbenimg/bb1 (2).png" alt="" />
+  </div>
+  <div class="update-item-content">
+    <h3>组件及相关样式</h3>
+    <ul>
+      <li>更新通知组件及相关样式,支持版本更新内容展示和图片预览功能;删除旧的更新内容文档并优化帮助菜单结构。</li>
+      <li>构新手引导逻辑,添加展开/折叠功能并持久化状态到localStorage;在用户重新登录时重置引导状态。 超时回退</li>
+      <li>支持显示来自Pilot 2 App上传的L3点云文件</li>
+    </ul>
+  </div>
+</div>
+
+### 【文件上传】
+
+<div class="update-item">
+  <div class="update-item-image">
+    <img src="/banbenimg/bb1 (3).png" alt="" />
+  </div>
+  <div class="update-item-content">
+     <h3><a href="/help/knowledge/intro#上传文档" target="_blank">知识库文件上传优化</a></h3>
+    <ul>
+      <li>加文件上传功能,创建专用的axios实例以处理上传请求</li>
+      <li>优化上传过程中的错误提示和进度反馈</li>
+      <li>添加上传过程中的加载提示并优化其样式</li>
+    </ul>
+  </div>
+</div>
+
+### 【网络处理】
+
+<div class="update-item">
+  <div class="update-item-image">
+    <img src="/banbenimg/bb1 (1).png" alt="" />
+  </div>
+  <div class="update-item-content">
+      <h3>网络处理及错误提示</h3>
+    <ul>
+      <li>增强403和504错误处理逻辑</li>
+      <li>隐藏上传列表以避免显示HTML错误信息的tooltip</li>
+    </ul>
+  </div>
+</div>
+
+
+---
+
+## 📅 2025-11-04 版本公告 (v1.1.0)
+
+### 【新增功能】
+
+<div class="update-item">
+  <div class="update-item-image">
+    <img src="/ed1.png" alt="应用广场优化" />
+  </div>
+  <div class="update-item-content">
+    <h3>应用广场优化</h3>
+    <p>全新的应用卡片设计,更清晰的信息展示</p>
+  </div>
+</div>
+
+<div class="update-item">
+  <div class="update-item-image">
+    <img src="/kg2.png" alt="知识库文件上传优化" />
+  </div>
+  <div class="update-item-content">
+    <h3>知识库文件上传优化</h3>
+    <p>收到的文件超时报错优化提示,上传文件过程中提示语优化</p>
+  </div>
+</div>
+
+### 【性能优化】
+
+<div class="update-item">
+  <div class="update-item-image">
+    <img src="/kg4.png" alt="文档上传速度提升" />
+  </div>
+  <div class="update-item-content">
+    <h3>文档上传速度提升</h3>
+    <p>文档上传速度提升 <strong>50%</strong>,错误信息优化<strong>居中显示</strong>,界面渲染优化,操作更流畅</p>
+  </div>
+</div>
+
+### 【问题修复】
+
+<div class="update-item">
+  <div class="update-item-image">
+    <img src="/ed2.png" alt="403报错修复" />
+  </div>
+  <div class="update-item-content">
+    <h3>修复403报错拦截及提示</h3>
+    <p>优化了错误处理机制,提供更友好的错误提示</p>
+  </div>
+</div>
+
+---
+
+感谢您的使用!如有问题请联系管理员 💬

+ 6 - 0
src/help/menu.ts

@@ -84,6 +84,12 @@ export const helpMenu: HelpMenuItem[] = [
       },
     ],
   },
+  {
+    key: 'update-history',
+    title: '更新历史',
+    path: '/help/update-history',
+    doc: 'update-history/index.md',
+  },
   // {
   //   key: 'dataExport',
   //   title: '数据导出',

+ 5 - 0
src/pages/deepseek/questionAnswer/list/index.tsx

@@ -42,6 +42,8 @@ import audit from '../../audit';
 import { set } from 'mobx';
 import IconSvg from "@/assets/public/icon.svg";
 import dayjs from 'dayjs';
+import UpdateNotification from '@/help/components/UpdateNotification';
+import { CURRENT_VERSION } from '@/help/components/UpdateNotification/version';
 
 const { Header, Footer, Sider, Content } = Layout;
 const { Option } = Select;
@@ -590,6 +592,9 @@ const QuestionAnswerList: React.FC = () => {
 
   return (
     <div>
+      {/* 更新通知弹窗 - 只在此页面显示 */}
+      <UpdateNotification version={CURRENT_VERSION} />
+      
       <div style={{ padding: '16px 20px', display: 'flex' }}>
         <Form
           form={form}

+ 17 - 1
src/pages/layout/components/Header.tsx

@@ -1,6 +1,7 @@
 import * as React from 'react';
 import { Layout, MenuProps, Modal, Dropdown, Select, Button, Tooltip } from 'antd';
 import { CaretDownOutlined, LogoutOutlined } from '@ant-design/icons';
+import { useNavigate, useLocation } from 'react-router-dom';
 import logoSrc from '@/assets/public/logo.png';
 import router from '@/router';
 import LocalStorage from '@/LocalStorage';
@@ -22,6 +23,10 @@ const Header: React.FC<Props> = (props: Props) => {
         currentMenuType
     } = props;
 
+    const navigate = useNavigate();
+    const location = useLocation();
+    const isHelp = location.pathname.startsWith('/help');
+
     // const items: MenuProps['items'] = [
     //     // 移除退出登录选项,现在使用独立的登出按钮
     // ];
@@ -35,11 +40,22 @@ const Header: React.FC<Props> = (props: Props) => {
         setOpen(visible);
     };
 
+    const handleLogoClick = () => {
+        if (isHelp) {
+            // 在帮助文档页面,点击跳转到首页
+            navigate('/deepseek/questionAnswer');
+        }
+    };
+
     return (
         <AntdHeader className='header'>
             <div className='header-logo'>
                 <img className='header-logo-picture' src={logoSrc} />
-                <div className='header-logo-text'>
+                <div 
+                    className='header-logo-text'
+                    onClick={handleLogoClick}
+                    style={isHelp ? { cursor: 'pointer', userSelect: 'none' } : {}}
+                >
                     建科•小智应用广场
                 </div>
                 {/* <Select