Przeglądaj źródła

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

* 'dev/newpersission' of https://git.zyuas.com/LLM/chat-admin-web:
  优化文件上传提示信息,增强403和504错误处理逻辑,添加上传过程中的加载提示,改进问答列表的分页和搜索功能。
  优化文件上传组件,隐藏上传列表以避免显示HTML错误信息的tooltip;增强问答列表的前端过滤功能,支持关键字搜索并保存原始数据以提高性能。
Ryuiso 3 dni temu
rodzic
commit
6412ae483c

+ 27 - 6
src/pages/deepseek/knowledgeLib/detail/drawerIndex.tsx

@@ -68,6 +68,7 @@ const KnowledgeLibInfo : React.FC<Props> = ({drawerItem}:Props) => {
 
   const [ fileList, setFileList ] = React.useState<any[]>( [] );
   const [ uploading, setUploading ] = React.useState( false );
+  const uploadMessageRef = React.useRef<(() => void) | null>( null );
 
   const [ sListFlag, setSListFlag ] = React.useState<boolean>();
   const [ cUpdateFlag, setCUpdateFlag ] = React.useState<boolean>();
@@ -126,14 +127,26 @@ const KnowledgeLibInfo : React.FC<Props> = ({drawerItem}:Props) => {
 
     },
     onChange( info ) {
-      const { status } = info.file;
+      const { status, name } = info.file;
 
-      if ( status !== 'uploading' ) {
-        console.log( status, 'status--uploading' );
-      }
-      if ( status === 'done' ) {
+      if ( status === 'uploading' ) {
+        // 文件开始上传,显示持续提示
+        if ( !uploadMessageRef.current ) {
+          const closeMessage = message.loading(
+            `为了确保文件 "${name}" 的解析质量,系统需要一些时间进行准备(通常为2-20分钟)。辛苦您耐心等待,您可以继续使用其他功能,只要不关闭当前页面即可。`,
+            0 // 0表示不自动关闭
+          );
+          uploadMessageRef.current = closeMessage;
+        }
+        setUploadLoading( true );
+      } else if ( status === 'done' ) {
         console.log( status, 'status--done' );
         console.info( info.file.response, 'info.file.response.data' );
+        // 关闭上传提示
+        if ( uploadMessageRef.current ) {
+          uploadMessageRef.current();
+          uploadMessageRef.current = null;
+        }
         if ( info.file.response.code === 200 && info.file.response.data === 1 ) {
           message.success( `${ info.file.name } file uploaded successfully.` );
           init( params.knowledgeId );
@@ -141,6 +154,11 @@ const KnowledgeLibInfo : React.FC<Props> = ({drawerItem}:Props) => {
         setUploadLoading( false );
       } else if ( status === 'error' ) {
         console.log( status, 'status--error' );
+        // 关闭上传提示
+        if ( uploadMessageRef.current ) {
+          uploadMessageRef.current();
+          uploadMessageRef.current = null;
+        }
         // 检查是否是504超时错误
         const response = info.file.response;
         const error = info.file.error;
@@ -154,6 +172,9 @@ const KnowledgeLibInfo : React.FC<Props> = ({drawerItem}:Props) => {
             responseStr.includes('<h1>504 Gateway Time-out</h1>') ||
             errorStr.includes('timeout') || 
             errorStr.includes('504')) {
+          // 替换文件对象中的错误信息,避免显示HTML内容
+          info.file.response = '上传文件超时,请修改文件后再上传';
+          info.file.error = '上传文件超时,请修改文件后再上传';
           message.error( '上传文件超时,请修改文件后再上传' );
         } else {
           message.error( `${ info.file.name } 文件上传失败` );
@@ -191,7 +212,7 @@ const KnowledgeLibInfo : React.FC<Props> = ({drawerItem}:Props) => {
       if (err?.response?.status === 504 || err?.code === 'ECONNABORTED' || String(err).includes('timeout')) {
         message.error( '上传文件超时,请修改文件后再上传' );
       } else {
-        message.error( '上传失败' );
+      message.error( '上传失败' );
       }
     } finally {
       setUploading( false );

+ 30 - 8
src/pages/deepseek/knowledgeLib/detail/index.tsx

@@ -65,6 +65,7 @@ const KnowledgeLibInfo : React.FC = () => {
 
   const [ fileList, setFileList ] = React.useState<any[]>( [] );
   const [ uploading, setUploading ] = React.useState( false );
+  const uploadMessageRef = React.useRef<(() => void) | null>( null );
 
   const [ sListFlag, setSListFlag ] = React.useState<boolean>();
   const [ cUpdateFlag, setCUpdateFlag ] = React.useState<boolean>();
@@ -126,14 +127,26 @@ const KnowledgeLibInfo : React.FC = () => {
 
     },
     onChange( info ) {
-      const { status } = info.file;
+      const { status, name } = info.file;
 
-      if ( status !== 'uploading' ) {
-        console.log( status, 'status--uploading' );
-      }
-      if ( status === 'done' ) {
+      if ( status === 'uploading' ) {
+        // 文件开始上传,显示持续提示
+        if ( !uploadMessageRef.current ) {
+          const closeMessage = message.loading(
+            `为了确保文件 "${name}" 的解析质量,系统需要一些时间进行准备(通常为2-20分钟)。辛苦您耐心等待,您可以继续使用其他功能,只要不关闭当前页面即可。`,
+            0 // 0表示不自动关闭
+          );
+          uploadMessageRef.current = closeMessage;
+        }
+        setUploadLoading( true );
+      } else if ( status === 'done' ) {
         console.log( status, 'status--done' );
         console.info( info.file.response, 'info.file.response.data' );
+        // 关闭上传提示
+        if ( uploadMessageRef.current ) {
+          uploadMessageRef.current();
+          uploadMessageRef.current = null;
+        }
         if ( info.file.response.code === 200 && info.file.response.data === 1 ) {
           message.success( `${ info.file.name } file uploaded successfully.` );
           init( params.knowledgeId );
@@ -141,6 +154,11 @@ const KnowledgeLibInfo : React.FC = () => {
         setUploadLoading( false );
       } else if ( status === 'error' ) {
         console.log( status, 'status--error' );
+        // 关闭上传提示
+        if ( uploadMessageRef.current ) {
+          uploadMessageRef.current();
+          uploadMessageRef.current = null;
+        }
         // 检查是否是504超时错误
         const response = info.file.response;
         const error = info.file.error;
@@ -154,6 +172,8 @@ const KnowledgeLibInfo : React.FC = () => {
             responseStr.includes('<h1>504 Gateway Time-out</h1>') ||
             errorStr.includes('timeout') || 
             errorStr.includes('504')) {
+          info.file.response = '上传文件超时,请修改文件后再上传';
+          info.file.error = '上传文件超时,请修改文件后再上传';
           message.error( '上传文件超时,请修改文件后再上传' );
         } else {
           message.error( `${ info.file.name } 文件上传失败` );
@@ -191,7 +211,7 @@ const KnowledgeLibInfo : React.FC = () => {
       if (err?.response?.status === 504 || err?.code === 'ECONNABORTED' || String(err).includes('timeout')) {
         message.error( '上传文件超时,请修改文件后再上传' );
       } else {
-        message.error( '上传失败' );
+      message.error( '上传失败' );
       }
     } finally {
       setUploading( false );
@@ -411,6 +431,7 @@ const KnowledgeLibInfo : React.FC = () => {
                     title={<span style={{ fontWeight: 600 }}>上传知识文档</span>}
                   >
                     <div style={{ color: '#666', fontSize: 12 }}>支持多种格式(Word、PDF、图片等),单个/总大小有上限,上传后自动处理(约5-10分钟,如遇到超时,可等待几分钟后刷新页面,将自动成功)。</div>
+                    {/* <div style={{ color: '#666', fontSize: 12 }}>支持pdf,doc,docx文件格式上传,建议单个文件不要超过10M,页数控制在100页以内。尽量单文件上传。</div> */}
                     <div style={{ textAlign: 'right', color: '#1677ff', fontWeight: 600 }}>step 1</div>
                   </Card>
                 </Col>
@@ -476,9 +497,10 @@ const KnowledgeLibInfo : React.FC = () => {
                           点击上传,或拖放文件到此处
                         </p>
                         <p className="ant-upload-hint">
-                          支持文件格式md,txt,pdf,jpg,png,jpeg,docx,xlsx,
+                          {/* 支持文件格式md,txt,pdf,jpg,png,jpeg,docx,xlsx,
                           pptx,eml,csv,单个文档 ≤ 30M,单张图片 ≤ 2.5M,文件总
-                          大小不得超过125M.
+                          大小不得超过125M. */}
+                          支持pdf,doc,docx,jpg,png,jpeg文件格式上传,建议单个文件不要超过10M,页数控制在100页以内。尽量单文件上传。
                         </p>
                       </Dragger>
                     </div>

+ 125 - 20
src/pages/deepseek/questionAnswer/list/index.tsx

@@ -115,6 +115,7 @@ const QuestionAnswerList: React.FC = () => {
 
   const [listLoading, setListLoading] = React.useState(false);
   const [list, setList] = React.useState<Item[]>([]);
+  const [originalList, setOriginalList] = React.useState<Item[]>([]); // 保存原始列表数据,用于前端过滤
   const [page, setPage] = React.useState<PageInfo>({
     pageNumber: 1,
     pageSize: 10,
@@ -141,22 +142,32 @@ const QuestionAnswerList: React.FC = () => {
   // 搜索输入框展开状态
   const [isSearchExpanded, setIsSearchExpanded] = React.useState(false);
   const searchWrapperRef = React.useRef<HTMLDivElement>(null);
+  // 标记是否正在重置,避免触发 useEffect 循环
+  const isResettingRef = React.useRef(false);
 
   const appApi = {
-    fetchList: async (typeId: any, projectId: any) => {
+    fetchList: async (typeId: any, projectId: any, forceRefresh: boolean = false, pageNumber?: number) => {
+      const keyword = form.getFieldValue('keyword');
+      const currentPageNumber = pageNumber !== undefined ? pageNumber : page.pageNumber;
+      
+      // 如果有关键字且不是强制刷新,直接从现有数据筛选,不请求接口
+      if (keyword && keyword.trim() !== '' && !forceRefresh) {
+        applyFrontendFilter(originalList, keyword, currentPageNumber);
+        return;
+      }
+
       setListLoading(true);
       try {
         const userInfo = LocalStorage.getUserInfo();
         const userId = (userInfo?.id ?? '').toString();
-        const keyword = form.getFieldValue('keyword');
         const res = await apis.fetchTakaiAppList({
           pageSize: page.pageSize,
-          pageNumber: page.pageNumber,
+          pageNumber: currentPageNumber,
           userId: userId,
           typeId: typeId,
           projectId: projectId?.toString(),
-          keyword: keyword,
-          name: keyword,
+          keyword: '', // 不传关键字给后端
+          name: '', // 不传name给后端
         })
         const list = res.rows.map((item: any) => {
           return {
@@ -182,9 +193,12 @@ const QuestionAnswerList: React.FC = () => {
           // 没有权限时排除 status='5' 的数据
           return item.status !== '5';
         });
+        // 保存原始数据
+        setOriginalList(filteredList);
+        // 没有关键字时,显示当前页的数据
         setList(filteredList);
         setPage({
-          pageNumber: page.pageNumber,
+          pageNumber: currentPageNumber,
           pageSize: page.pageSize,
           total: res.total,
         });
@@ -224,14 +238,15 @@ const QuestionAnswerList: React.FC = () => {
   }
 
   const indexApi = {
-    fetchIndex: async (typeId: any, projectId: any) => {
+    fetchIndex: async (typeId: any, projectId: any, pageNumber?: number) => {
       try {
         const userInfo = LocalStorage.getUserInfo();
         const userId = (userInfo?.id ?? '').toString();
         const keyword = form.getFieldValue('keyword');
+        const currentPageNumber = pageNumber !== undefined ? pageNumber : page.pageNumber;
         const res = await apis.fetchTakaiIndexCount({
           pageSize: page.pageSize,
-          pageNumber: page.pageNumber,
+          pageNumber: currentPageNumber,
           userId: userId,
           typeId: typeId,
           projectId: projectId?.toString(),
@@ -333,7 +348,7 @@ const QuestionAnswerList: React.FC = () => {
   };
 
   const init = async () => {
-    await appApi.fetchList(null, null);
+    await appApi.fetchList(null, null, true); // 强制刷新
     await indexApi.fetchIndex(null, null);
     await appTypeApi.fetchAppType();
     await projectApi.fetchProject();
@@ -345,6 +360,11 @@ const QuestionAnswerList: React.FC = () => {
   }
 
   React.useEffect(() => {
+    // 如果正在重置,跳过执行,避免循环
+    if (isResettingRef.current) {
+      isResettingRef.current = false;
+      return;
+    }
     setCreateFlag(LocalStorage.getStatusFlag('deepseek:application:create'));
     setDeleteFlag(LocalStorage.getStatusFlag('deepseek:application:delete'));
     setUpdateFlag(LocalStorage.getStatusFlag('deepseek:application:update'));
@@ -367,17 +387,82 @@ const QuestionAnswerList: React.FC = () => {
     pageSize: page.pageSize,
     total: page.total,
     onChange: (pageNumber, pageSize) => {
+      const keyword = form.getFieldValue('keyword');
+      // 根据是否有搜索关键字,决定使用哪个数据源
+      const dataSource = keyword && keyword.trim() !== '' 
+        ? originalList.filter((item: Item) => 
+            item.name && item.name.toLowerCase().includes(keyword.toLowerCase().trim())
+          )
+        : originalList;
+      
+      // 获取新页的数据
+      const startIndex = (pageNumber - 1) * pageSize;
+      const endIndex = startIndex + pageSize;
+      const paginatedData = dataSource.slice(startIndex, endIndex);
+      
+      setList(paginatedData);
       setPage({
         pageNumber: pageNumber,
         pageSize: pageSize,
-        total: page.total,
+        total: dataSource.length,
       });
     },
   };
 
+  // 前端模糊查询过滤函数
+  const applyFrontendFilter = (dataList: Item[], keyword: string | undefined, currentPageNumber: number = 1) => {
+    if (!keyword || keyword.trim() === '') {
+      // 如果没有关键字,显示所有数据
+      const paginatedData = getPaginatedData(dataList, currentPageNumber, page.pageSize);
+      setList(paginatedData);
+      setPage(prev => ({
+        ...prev,
+        total: dataList.length,
+        pageNumber: currentPageNumber,
+      }));
+      return;
+    }
+
+    // 对标题进行模糊匹配(不区分大小写)
+    const filtered = dataList.filter((item: Item) => {
+      return item.name && item.name.toLowerCase().includes(keyword.toLowerCase().trim());
+    });
+
+    // 计算过滤后的总页数
+    const totalPages = Math.ceil(filtered.length / page.pageSize);
+    // 如果当前页码超出了总页数,调整到最后一页(至少为1)
+    const adjustedPageNumber = Math.min(currentPageNumber, Math.max(1, totalPages));
+
+    // 获取调整后页码的数据
+    const paginatedData = getPaginatedData(filtered, adjustedPageNumber, page.pageSize);
+    setList(paginatedData);
+    setPage(prev => ({
+      ...prev,
+      total: filtered.length,
+      pageNumber: adjustedPageNumber,
+    }));
+  };
+
+  // 获取分页数据
+  const getPaginatedData = (dataList: Item[], pageNumber: number, pageSize: number) => {
+    const startIndex = (pageNumber - 1) * pageSize;
+    const endIndex = startIndex + pageSize;
+    return dataList.slice(startIndex, endIndex);
+  };
+
   // 点击查询
   const handleClickSearch = async () => {
     form.validateFields().then(async (values) => {
+      const keyword = values.keyword;
+      
+      // 如果有关键字,直接从现有数据中筛选,不请求接口
+      if (keyword && keyword.trim() !== '') {
+        // 模糊查询:从现有数据中筛选,保持当前页码
+        applyFrontendFilter(originalList, keyword, page.pageNumber);
+        return;
+      }
+
+      // 如果没有关键字,正常查询接口
       if (values.proTypeId) {
         values.typeId = values.proTypeId;
       }
@@ -387,8 +472,10 @@ const QuestionAnswerList: React.FC = () => {
       if (values.projectId instanceof Array && values.projectId.length == 2) {
         values.projectId = values.projectId[1];
       }
+      // 重置到第一页
+      setPage(prev => ({ ...prev, pageNumber: 1 }));
       await indexApi.fetchIndex(values.typeId, values.projectId);
-      await appApi.fetchList(values.typeId, values.projectId);
+      await appApi.fetchList(values.typeId, values.projectId, true); // 强制刷新
     }).catch((error) => {
       console.error(error);
     });
@@ -399,10 +486,17 @@ const QuestionAnswerList: React.FC = () => {
     form.resetFields();
     setShowSubPanel(false);
     setSelectedType('全部'); // 重置为"全部"
-    page.pageNumber = 1;
-    page.pageSize = 10;
-    await appApi.fetchList(null, null);
-    await indexApi.fetchIndex(null, null);
+    // 标记正在重置,避免触发 useEffect 循环
+    isResettingRef.current = true;
+    // 先更新 page 到第一页
+    setPage({
+      pageNumber: 1,
+      pageSize: 10,
+      total: 0,
+    });
+    // 调用接口获取数据,传递 pageNumber: 1 确保返回第一页
+    await appApi.fetchList(null, null, true, 1); // 强制刷新,第一页
+    await indexApi.fetchIndex(null, null, 1); // 第一页
   };
 
   /** 点击外部关闭面板 */
@@ -435,11 +529,11 @@ const QuestionAnswerList: React.FC = () => {
     // 自动提交逻辑
     if (value === '全部') {
       // 全部选项,传递null给后端
-      appApi.fetchList(null, null);
+      appApi.fetchList(null, null, true); // 强制刷新
       indexApi.fetchIndex(null, null);
     } else {
       // 其他选项,传递对应的typeId
-      appApi.fetchList(value, null);
+      appApi.fetchList(value, null, true); // 强制刷新
       indexApi.fetchIndex(value, null);
     }
   };
@@ -521,7 +615,7 @@ const QuestionAnswerList: React.FC = () => {
                   onChange={(value) => {
                     // 项目类型选择器自动提交逻辑
                     const currentProjectId = form.getFieldValue('projectId');
-                    appApi.fetchList(value, currentProjectId);
+                    appApi.fetchList(value, currentProjectId, true); // 强制刷新
                     indexApi.fetchIndex(value, currentProjectId);
                   }}
                 >
@@ -549,6 +643,17 @@ const QuestionAnswerList: React.FC = () => {
                 options={appProjectList}
                 placeholder="请选择项目"
                 showSearch
+                onChange={(value) => {
+                  // 项目选择器自动提交逻辑
+                  const currentProTypeId = form.getFieldValue('proTypeId');
+                  let projectId: any = value;
+                  // 如果是数组且长度为2,取第二个元素(项目ID)
+                  if (projectId instanceof Array && projectId.length === 2) {
+                    projectId = projectId[1];
+                  }
+                  appApi.fetchList(currentProTypeId, projectId, true); // 强制刷新
+                  indexApi.fetchIndex(currentProTypeId, projectId);
+                }}
               />
             </FormItem>
           )}
@@ -574,7 +679,7 @@ const QuestionAnswerList: React.FC = () => {
           {/* </FormItem> */}
           <FormItem>
             <Space size={12}>
-              <div 
+              {/* <div 
                 className="search-expand-wrapper"
                 ref={searchWrapperRef}
                 onMouseEnter={() => setIsSearchExpanded(true)}
@@ -613,7 +718,7 @@ const QuestionAnswerList: React.FC = () => {
                   icon={<SearchOutlined />}
                   className="search-button"
                 />
-              </div>
+              </div> */}
               <Tooltip title="重置">
                 <Button
                   shape="circle"

+ 13 - 0
src/pages/layout/style.less

@@ -396,6 +396,7 @@
                 background-color: #e9ecef;
                 color: #409eff;
                 box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+                transform: translateY(-2px);
             }
             
             &:active {
@@ -406,6 +407,18 @@
                 margin-right: 4px;
             }
         }
+        
+        .ant-btn-primary {
+            border-radius: 6px;
+            font-weight: 500;
+            box-shadow: 0 2px 4px rgba(24, 144, 255, 0.2);
+            transition: all 0.3s ease;
+            
+            &:hover {
+                transform: translateY(-2px);
+                box-shadow: 0 4px 8px rgba(24, 144, 255, 0.3);
+            }
+        }
     }
 
     /* 内容区域滚动 */