优化客户端请求时候操作的页面不更新

This commit is contained in:
2025-06-06 10:21:14 +08:00
parent ce4e621741
commit 358e9ab745
10 changed files with 162 additions and 81 deletions
+55 -25
View File
@@ -14,8 +14,8 @@ export type ApiResponse<T> = {
export type QueryParams = Record<string, string | number | boolean | undefined>;
// 获取 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<AxiosResponse> {
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<T>(
}
}
// 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<T>(
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<string, string> = {};
@@ -345,7 +375,7 @@ export async function downloadFile(path: string): Promise<Blob> {
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'
});
+5 -5
View File
@@ -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'}`;
}
}
+10 -9
View File
@@ -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<T, D extends object>(
headers: {
'Prefer': 'return=representation'
}
}
},
queryParams
);
if (response.error) {
+9 -9
View File
@@ -82,11 +82,11 @@ const ChatSidebar = forwardRef<ChatSidebarRef, ChatSidebarProps>(({
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<ChatSidebarRef, ChatSidebarProps>(({
// 通知父组件会话已删除
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<ChatSidebarRef, ChatSidebarProps>(({
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<ChatSidebarRef, ChatSidebarProps>(({
// 通知父组件会话已重命名
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<ChatSidebarRef, ChatSidebarProps>(({
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<ChatSidebarRef, ChatSidebarProps>(({
// 通知父组件会话已重命名
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 || '重命名会话失败');
}
+7 -7
View File
@@ -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);
};
/**
+5 -5
View File
@@ -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,
+19 -2
View File
@@ -175,6 +175,9 @@ export default function DocumentsIndex() {
const [filteredDocumentTypeOptions, setFilteredDocumentTypeOptions] = useState(loaderData.documentTypeOptions);
const dataCache = useRef<typeof loaderData | null>(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');
+6 -7
View File
@@ -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() {
<div className="login-form-container">
<h2 className="login-subtitle"></h2>
<form
action="/login"
<Form
method="post"
className="login-form"
>
@@ -144,7 +143,7 @@ export default function Login() {
>
</button>
</form>
</Form>
</div>
<div className="login-footer">
+27 -5
View File
@@ -249,6 +249,28 @@ export default function RuleNew() {
setInstanceKey(`new_${Date.now()}`);
}, []);
/**
* 从API响应中提取数据
* @param responseData - API响应数据
* @returns 提取的数据或null
*/
function extractApiData<T>(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<EvaluationPoint[]>(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);
+19 -7
View File
@@ -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);