From 358e9ab745a59b5ac3535ebcabff619559d9892a Mon Sep 17 00:00:00 2001 From: yorn <1057707203@qq.com> Date: Fri, 6 Jun 2025 10:21:14 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=AE=A2=E6=88=B7=E7=AB=AF?= =?UTF-8?q?=E8=AF=B7=E6=B1=82=E6=97=B6=E5=80=99=E6=93=8D=E4=BD=9C=E7=9A=84?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E4=B8=8D=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/axios-client.ts | 80 ++++++++++++++++++++++----------- app/api/files/documents.ts | 10 ++--- app/api/postgrest-client.ts | 19 ++++---- app/components/chat/sidebar.tsx | 18 ++++---- app/hooks/use-conversation.ts | 14 +++--- app/root.tsx | 10 ++--- app/routes/documents._index.tsx | 21 ++++++++- app/routes/login.tsx | 13 +++--- app/routes/rules-new.tsx | 32 ++++++++++--- app/routes/rules._index.tsx | 26 ++++++++--- 10 files changed, 162 insertions(+), 81 deletions(-) diff --git a/app/api/axios-client.ts b/app/api/axios-client.ts index 1c1be83..26b2b03 100644 --- a/app/api/axios-client.ts +++ b/app/api/axios-client.ts @@ -14,8 +14,8 @@ export type ApiResponse = { export type QueryParams = Record; // 获取 API 基础 URL -const API_BASE_URL = 'http://172.16.0.58:8008'; -// const API_BASE_URL = 'http://nas.7bm.co:3000'; +// const API_BASE_URL = 'http://172.16.0.58:8008'; +const API_BASE_URL = 'http://nas.7bm.co:3000'; // const API_BASE_URL = 'http://172.18.0.100:3000'; // const API_BASE_URL = 'http://172.16.0.119:9000/admin'; @@ -25,39 +25,66 @@ export const DOCUMENT_URL = 'http://nas.7bm.co:9000/docauditai/'; // 是否使用模拟数据(开发环境使用) const USE_MOCK_DATA = false; // 设置为true使用模拟数据,避免API连接问题 +// 设置超时时间(毫秒) +const DEFAULT_TIMEOUT = 30000; // 增加到30秒 + // 创建 axios 实例 const axiosInstance = axios.create({ baseURL: API_BASE_URL, - timeout: 10000, // 10秒超时 + timeout: DEFAULT_TIMEOUT, // 增加超时时间 headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' } }); +// 最大重试次数 +const MAX_RETRIES = 2; + /** - * 将编码后的URL解码为可读格式 - * @param url 编码后的URL - * @returns 解码后的可读URL + * 带重试功能的请求方法 + * @param config Axios请求配置 + * @param retries 当前重试次数 + * @returns Axios响应 */ -function decodeUrlForDisplay(url: string): string { +async function axiosRetry(config: AxiosRequestConfig, retries = 0): Promise { try { - // 首先解码整个URL - const decodedUrl = decodeURIComponent(url); - - // 如果URL中包含@符号作为前缀,则移除它 - if (decodedUrl.startsWith('@')) { - return decodedUrl.substring(1); - } - - return decodedUrl; + return await axiosInstance(config); } catch (error) { - // 如果解码失败,返回原始URL - console.error('URL解码失败:', error); - return url; + if (isAxiosError(error) && error.code === 'ECONNABORTED' && retries < MAX_RETRIES) { + console.log(`请求超时,第${retries + 1}次重试...`); + // 递增重试次数并重新发送请求 + return axiosRetry(config, retries + 1); + } + throw error; } } +/** + * 将编码后的URL解码为可读格式 + * 当前已注释掉日志,暂未使用此函数 + * @param url 编码后的URL + * @returns 解码后的可读URL + * @unused 保留供将来使用 + */ +// function decodeUrlForDisplay(url: string): string { +// try { +// // 首先解码整个URL +// const decodedUrl = decodeURIComponent(url); +// +// // 如果URL中包含@符号作为前缀,则移除它 +// if (decodedUrl.startsWith('@')) { +// return decodedUrl.substring(1); +// } +// +// return decodedUrl; +// } catch (error) { +// // 如果解码失败,返回原始URL +// console.error('URL解码失败:', error); +// return url; +// } +// } + /** * 构建完整的 API URL */ @@ -188,8 +215,8 @@ export async function apiRequest( } } - // console.log(`📦 axios-client.ts->请求URL: ${decodeUrlForDisplay(url)}`); - // console.log(`axios-client.ts->发送 ${options.method || 'GET'} 请求到: ${decodeUrlForDisplay(url)}`); + // console.log(`📦 axios-client.ts->请求URL: ${url}`); + // console.log(`axios-client.ts->发送 ${options.method || 'GET'} 请求到: ${url}`); // 处理body参数,转换为data if (options.body) { @@ -203,16 +230,19 @@ export async function apiRequest( const config: AxiosRequestConfig = { ...options, url, - headers + headers, + // 确保使用默认超时时间 + timeout: options.timeout || DEFAULT_TIMEOUT }; - console.log(`📦 axios-client.ts->请求配置: \n${JSON.stringify(config)}`); + // console.log(`📦 axios-client.ts->请求配置: \n${JSON.stringify(config)}`); // 删除body属性,避免axios警告 if ('body' in config) { delete (config as ExtendedAxiosRequestConfig).body; } - const response: AxiosResponse = await axiosInstance(config); + // 使用带重试功能的请求方法 + const response: AxiosResponse = await axiosRetry(config); // 收集响应头信息 const responseHeaders: Record = {}; @@ -345,7 +375,7 @@ export async function downloadFile(path: string): Promise { const downloadUrl = `${DOCUMENT_URL}${path}`; try { - // console.log(`📦 axios-client.ts->下载文件: ${decodeUrlForDisplay(downloadUrl)}`); + // console.log(`📦 axios-client.ts->下载文件: ${downloadUrl}`); const response = await axios.get(downloadUrl, { responseType: 'blob' }); diff --git a/app/api/files/documents.ts b/app/api/files/documents.ts index 177b50f..7a7d74c 100644 --- a/app/api/files/documents.ts +++ b/app/api/files/documents.ts @@ -226,19 +226,19 @@ export async function getDocuments(searchParams: DocumentSearchParams = {}): Pro // 处理日期范围 if (searchParams.dateFrom) { // 添加当天开始时间 00:00:00 - filter['created_at'] = `gte.${searchParams.dateFrom + ' 00:00:00'}`; + filter['updated_at'] = `gte.${searchParams.dateFrom + ' 00:00:00'}`; } if (searchParams.dateTo) { // 如果有开始日期,使用and条件;否则直接设置结束日期 - const dateToKey = searchParams.dateFrom ? 'and' : 'created_at'; + const dateToKey = searchParams.dateFrom ? 'and' : 'updated_at'; // 添加当天结束时间 23:59:59 if (dateToKey === 'and') { - delete filter['created_at']; + delete filter['updated_at']; // 使用OR操作符连接两个条件 - filter[dateToKey] = `(created_at.gte.${searchParams.dateFrom+' 00:00:00'},created_at.lte.${searchParams.dateTo+' 23:59:59'})`; + filter[dateToKey] = `(updated_at.gte.${searchParams.dateFrom+' 00:00:00'},updated_at.lte.${searchParams.dateTo+' 23:59:59'})`; } else { - filter['created_at'] = `lte.${searchParams.dateTo+' 23:59:59'}`; + filter['updated_at'] = `lte.${searchParams.dateTo+' 23:59:59'}`; } } diff --git a/app/api/postgrest-client.ts b/app/api/postgrest-client.ts index 93d984f..9dffb78 100644 --- a/app/api/postgrest-client.ts +++ b/app/api/postgrest-client.ts @@ -84,17 +84,17 @@ function logPostgrestQuery(endpoint: string, params?: QueryParams, method: strin } } - console.log('\n📦 PostgREST 查询日志 ========================'); - console.log(`📦 HTTP 方法: ${method}`); - console.log(`📦 完整 URL: ${decodeUrlForDisplay(fullUrl)}`); + // console.log('\n📦 PostgREST 查询日志 ========================'); + // console.log(`📦 HTTP 方法: ${method}`); + // console.log(`📦 完整 URL: ${decodeUrlForDisplay(fullUrl)}`); // 打印请求体数据(仅适用于POST/PATCH/PUT请求) - if (['POST', 'PATCH', 'PUT'].includes(method) && data) { - console.log('📦 请求体数据:'); - console.log(JSON.stringify(data, null, 2)); - } + // if (['POST', 'PATCH', 'PUT'].includes(method) && data) { + // console.log('📦 请求体数据:'); + // console.log(JSON.stringify(data, null, 2)); + // } - console.log('📦 PostgREST 查询日志 ========================\n'); + // console.log('📦 PostgREST 查询日志 ========================\n'); } } @@ -467,7 +467,8 @@ export async function postgrestPut( headers: { 'Prefer': 'return=representation' } - } + }, + queryParams ); if (response.error) { diff --git a/app/components/chat/sidebar.tsx b/app/components/chat/sidebar.tsx index 41e72f7..7428847 100644 --- a/app/components/chat/sidebar.tsx +++ b/app/components/chat/sidebar.tsx @@ -82,11 +82,11 @@ const ChatSidebar = forwardRef(({ setDeleteLoading(true); try { - console.log('🗑️ 开始删除会话:', deletingConversation.id); + // console.log('🗑️ 开始删除会话:', deletingConversation.id); // 调用API删除服务器端的会话 const response = await deleteConversation(deletingConversation.id); - console.log('✅ 服务器端会话删除响应:', response); + // console.log('✅ 服务器端会话删除响应:', response); // 检查响应是否成功 if (response && (response as any).result === 'success') { @@ -97,7 +97,7 @@ const ChatSidebar = forwardRef(({ // 通知父组件会话已删除 onConversationDeleted?.(deletingConversation.id); - console.log('✅ 会话删除完成:', deletingConversation.id); + // console.log('✅ 会话删除完成:', deletingConversation.id); } else { throw new Error((response as any)?.error || '删除会话失败'); } @@ -130,11 +130,11 @@ const ChatSidebar = forwardRef(({ setRenameLoading(true); try { - console.log('✏️ 开始重命名会话:', { conversationId: renamingConversation.id, newName: newName.trim() }); + // console.log('✏️ 开始重命名会话:', { conversationId: renamingConversation.id, newName: newName.trim() }); // 调用API重命名服务器端的会话 const response = await renameConversation(renamingConversation.id, newName.trim(), false); - console.log('✅ 服务器端会话重命名响应:', response); + // console.log('✅ 服务器端会话重命名响应:', response); // 检查响应是否成功 if (response && (response as any).name) { @@ -145,7 +145,7 @@ const ChatSidebar = forwardRef(({ // 通知父组件会话已重命名 onConversationRenamed?.(renamingConversation.id, (response as any).name); - console.log('✅ 会话重命名完成:', renamingConversation.id, '->', (response as any).name); + // console.log('✅ 会话重命名完成:', renamingConversation.id, '->', (response as any).name); } else { throw new Error((response as any)?.error || '重命名会话失败'); } @@ -212,11 +212,11 @@ const ChatSidebar = forwardRef(({ useImperativeHandle(ref, () => ({ autoRename: async (conversationId: string) => { try { - console.log('🏷️ 开始自动重命名会话为"新对话":', conversationId); + // console.log('🏷️ 开始自动重命名会话为"新对话":', conversationId); // 调用API将会话重命名为固定的"新对话" const response = await renameConversation(conversationId, '新对话', false); - console.log('✅ 服务器端会话重命名响应:', response); + // console.log('✅ 服务器端会话重命名响应:', response); // 检查响应是否成功 if (response && (response as any).name) { @@ -225,7 +225,7 @@ const ChatSidebar = forwardRef(({ // 通知父组件会话已重命名 onConversationRenamed?.(conversationId, (response as any).name); - console.log('✅ 会话重命名完成:', conversationId, '->', (response as any).name); + // console.log('✅ 会话重命名完成:', conversationId, '->', (response as any).name); } else { throw new Error((response as any)?.error || '重命名会话失败'); } diff --git a/app/hooks/use-conversation.ts b/app/hooks/use-conversation.ts index f8751e5..8f9294f 100644 --- a/app/hooks/use-conversation.ts +++ b/app/hooks/use-conversation.ts @@ -113,7 +113,7 @@ export default function useConversation() { isSetToLocalStorage = true, newConversationName = '' ) => { - console.log('🔄 设置当前会话ID:', { id, appId, isSetToLocalStorage }); + // console.log('🔄 设置当前会话ID:', { id, appId, isSetToLocalStorage }); doSetCurrConversationId(id); @@ -130,18 +130,18 @@ export default function useConversation() { globalThis.localStorage?.setItem(storageConversationIdKey, JSON.stringify(conversationIdInfo)); - console.log('💾 会话ID已保存到localStorage:', { - appUrlKey, - conversationId: id, - fullStorage: conversationIdInfo - }); + // console.log('💾 会话ID已保存到localStorage:', { + // appUrlKey, + // conversationId: id, + // fullStorage: conversationIdInfo + // }); } catch (error) { console.error('保存会话ID到本地存储失败:', error); } } // 不进行URL导航,保持单页面应用模式 - console.log('✅ 会话切换完成,当前会话ID:', id); + // console.log('✅ 会话切换完成,当前会话ID:', id); }; /** diff --git a/app/root.tsx b/app/root.tsx index 49ee049..9ad8aee 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -68,11 +68,11 @@ export async function getUserSession(request: Request) { const isAuthenticated = session.get("isAuthenticated") === true; const userRole = session.get("userRole") || 'common' as UserRole; - console.log("获取会话状态:", - // "Cookie:", request.headers.get("Cookie"), - "是否认证:", isAuthenticated, - "用户角色:", userRole - ); + // console.log("获取会话状态:", + // // "Cookie:", request.headers.get("Cookie"), + // "是否认证:", isAuthenticated, + // "用户角色:", userRole + // ); return { isAuthenticated, diff --git a/app/routes/documents._index.tsx b/app/routes/documents._index.tsx index 3e12dfc..8289207 100644 --- a/app/routes/documents._index.tsx +++ b/app/routes/documents._index.tsx @@ -175,6 +175,9 @@ export default function DocumentsIndex() { const [filteredDocumentTypeOptions, setFilteredDocumentTypeOptions] = useState(loaderData.documentTypeOptions); const dataCache = useRef(null); + // 添加一个状态来跟踪是否执行了删除操作 + const [isDeleting, setIsDeleting] = useState(false); + // 从URL获取当前筛选条件 const search = searchParams.get("search") || ""; const documentType = searchParams.get("documentType") || ""; @@ -283,14 +286,22 @@ export default function DocumentsIndex() { // 使用useEffect监听fetcher状态变化并显示Toast useEffect(() => { - if (fetcher.data && fetcher.state === 'idle') { + if (fetcher.data && fetcher.state === 'idle' && isDeleting) { + // 重置删除状态 + setIsDeleting(false); + if (fetcher.data.result) { toastService.success(fetcher.data.message); + // 删除成功后重新加载数据 + if (reviewType) { + fetchData(reviewType); + } } else if (fetcher.data.message) { toastService.error(fetcher.data.message); + // 删除失败只显示错误信息,不刷新数据 } } - }, [fetcher.data, fetcher.state]); + }, [fetcher.data, fetcher.state, fetchData, reviewType, isDeleting]); // 分页处理函数 const handlePageChange = (page: number) => { @@ -468,6 +479,9 @@ export default function DocumentsIndex() { confirmText: "删除", cancelText: "取消", onConfirm: () => { + // 设置删除状态为true + setIsDeleting(true); + const form = new FormData(); form.append("_action", "delete"); form.append("id", id); @@ -501,6 +515,9 @@ export default function DocumentsIndex() { confirmText: "删除", cancelText: "取消", onConfirm: () => { + // 设置删除状态为true + setIsDeleting(true); + // 使用fetcher提交表单 const formData = new FormData(); formData.append('_action', 'batchDelete'); diff --git a/app/routes/login.tsx b/app/routes/login.tsx index 55c1780..0d9921e 100644 --- a/app/routes/login.tsx +++ b/app/routes/login.tsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import { useActionData } from "@remix-run/react"; +import { useActionData, Form } from "@remix-run/react"; import { type MetaFunction, type ActionFunctionArgs, redirect, type LoaderFunctionArgs } from "@remix-run/node"; import styles from "~/styles/pages/login.css?url"; import { getUserSession, getSession, type UserRole, sessionStorage } from "~/root"; @@ -22,7 +22,7 @@ export async function action({ request }: ActionFunctionArgs) { const password = formData.get("password") as string; const userRole = formData.get("userRole") as UserRole || 'common'; - console.log("userRole-----", userRole); + // console.log("userRole-----", userRole); // 简单的登录验证,实际应用中应该进行真正的身份验证 if (!username || !password) { @@ -39,7 +39,7 @@ export async function action({ request }: ActionFunctionArgs) { const session = await getSession(request); // 查看session中存储的redirectTo值 const redirectTo = session.get("redirectTo") || "/"; - console.log("登录后重定向到:", redirectTo); + // console.log("登录后重定向到:", redirectTo); // 创建会话cookie const newSession = await sessionStorage.getSession(); @@ -47,7 +47,7 @@ export async function action({ request }: ActionFunctionArgs) { newSession.set("userRole", userRole); const cookie = await sessionStorage.commitSession(newSession); - console.log("设置cookie:", !!cookie); + // console.log("设置cookie:", !!cookie); // 使用新方法进行重定向 return redirect(redirectTo, { @@ -85,8 +85,7 @@ export default function Login() {

用户登录

-
@@ -144,7 +143,7 @@ export default function Login() { > 登录 -
+
diff --git a/app/routes/rules-new.tsx b/app/routes/rules-new.tsx index 47b420e..e18d437 100644 --- a/app/routes/rules-new.tsx +++ b/app/routes/rules-new.tsx @@ -249,6 +249,28 @@ export default function RuleNew() { setInstanceKey(`new_${Date.now()}`); }, []); + + /** + * 从API响应中提取数据 + * @param responseData - API响应数据 + * @returns 提取的数据或null + */ + function extractApiData(responseData: unknown): T | null { + if (!responseData) return null; + + // 格式1: { code: number, msg: string, data: T } + if (typeof responseData === 'object' && responseData !== null && + 'code' in responseData && + 'data' in responseData && + (responseData as { data: unknown }).data) { + return (responseData as { data: T }).data; + } + + // 格式2: 直接是数据对象 + return responseData as T; + } + + /** * 获取评查点数据 * 编辑模式下从API获取指定ID的评查点数据 @@ -266,17 +288,17 @@ export default function RuleNew() { }; const response = await postgrestGet('evaluation_points', postgrestParams); - if (response.data && Array.isArray(response.data) && response.data[0]) { + if (response.data) { + // 使用extractApiData从响应中提取数据 + const evaluationPoints = extractApiData(response.data); - if (response.data.length > 0) { + if (evaluationPoints && Array.isArray(evaluationPoints) && evaluationPoints.length > 0) { try { // 使用JSON序列化和反序列化来进行深拷贝,避免浏览器差异 - const originalData = response.data[0]; + const originalData = evaluationPoints[0]; const jsonString = JSON.stringify(originalData); const data = JSON.parse(jsonString); - // console.log("数据已经过深拷贝处理,避免浏览器兼容性问题"); - // 设置表单数据 setFormData(data); diff --git a/app/routes/rules._index.tsx b/app/routes/rules._index.tsx index 047a610..b27780e 100644 --- a/app/routes/rules._index.tsx +++ b/app/routes/rules._index.tsx @@ -185,6 +185,8 @@ export default function RulesIndex() { // 添加一个路由变化计数器 const [routeChangeCount, setRouteChangeCount] = useState(0); + // 添加一个状态来跟踪是否执行了删除操作 + const [isDeleting, setIsDeleting] = useState(false); // 获取当前的ruleType值 const ruleTypeParam = searchParams.get('ruleType'); @@ -203,11 +205,11 @@ export default function RulesIndex() { const isDeveloper = userRole === 'developer'; // 在组件渲染时初始化状态 - useEffect(() => { - setFilteredRules(initialRules); - setFilteredTotalCount(initialTotalCount); - setRuleTypes(initialRuleTypes); - }, [initialRules, initialTotalCount, initialRuleTypes]); + // useEffect(() => { + // setFilteredRules(initialRules); + // setFilteredTotalCount(initialTotalCount); + // setRuleTypes(initialRuleTypes); + // }, [initialRules, initialTotalCount, initialRuleTypes]); // 使用useEffect监听loaderData.error变化并显示Toast useEffect(() => { @@ -310,18 +312,26 @@ export default function RulesIndex() { // loading: 加载中状态 // idle: 空闲状态 useEffect(() => { - if (fetcher.data && fetcher.state === 'idle') { + // 仅在fetcher有数据且状态为idle时处理 + if (fetcher.data && fetcher.state === 'idle' && isDeleting) { + // 重置删除状态 + setIsDeleting(false); + if (fetcher.data.result) { toastService.success(fetcher.data.message); + // 删除成功后重新加载数据 + fetchData(); } else if (!fetcher.data.result) { + // 删除失败只显示错误信息,不刷新数据 if(fetcher.data.message.includes("evaluation_results_evaluation_point_id_fkey")) { toastService.error('对表evaluation_points进行更新或删除违反了表evaluation results上的外键约束evaluations results_evaluation _point_id_fkey'); } else { toastService.error(fetcher.data.message); } + // 删除失败不刷新数据 } } - }, [fetcher.data,fetcher.state]); + }, [fetcher.data, fetcher.state, fetchData, isDeleting]); // 在组件挂载时从 sessionStorage 获取 reviewType 并加载数据 useEffect(() => { @@ -434,6 +444,8 @@ export default function RulesIndex() { confirmText: "删除", cancelText: "取消", onConfirm: () => { + // 设置删除状态为true + setIsDeleting(true); const form = new FormData(); form.append("_action", "delete"); form.append("ruleId", rule.id);