修复系统概览数据不准确的查询。修复交叉评查意见列表的数量查询。优化全局消息提示的层级。优化提交意见进行局部更新。

This commit is contained in:
2025-07-25 09:49:36 +08:00
parent 3dab54d551
commit ccd5cdf71e
29 changed files with 2444 additions and 1035 deletions
+2 -2
View File
@@ -34,7 +34,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
try {
// 创建OAuth客户端
const oauthClient = new OAuthClient(OAUTH_CONFIG);
const oauthClient = new OAuthClient(OAUTH_CONFIG.value);
// 获取访问令牌
const tokenResponse = await oauthClient.getAccessToken(code);
@@ -130,4 +130,4 @@ export default function Callback() {
</div>
</div>
);
}
}
+15 -30
View File
@@ -262,35 +262,8 @@ export async function action({ request }: ActionFunctionArgs) {
return Response.json({ success: true, data: response.data });
}
if (intent === "submitCrossCheckingOpinion") {
const { submitCrossCheckingOpinion } = await import("~/api/cross-checking/cross-file-result");
const reviewPointResultId = formData.get("reviewPointResultId") as string;
const documentId = formData.get("documentId") as string;
const auditPoint = formData.get("auditPoint") as string;
const foundIssue = formData.get("foundIssue") as string;
const auditOpinion = formData.get("auditOpinion") as string;
const deductionScore = parseFloat(formData.get("deductionScore") as string);
const opinionData = {
reviewPointResultId,
documentId,
auditPoint,
foundIssue,
auditOpinion,
deductionScore
};
const response = await submitCrossCheckingOpinion(opinionData, frontendJWT);
if (response.error) {
return Response.json({ success: false, error: response.error }, { status: response.status || 500 });
}
return Response.json({ success: true, data: response.data });
}
if (intent === "getCrossCheckingOpinions") {
if (intent === "getCrossCheckingOpinions") {
const { getCrossCheckingOpinions } = await import("~/api/cross-checking/cross-file-result");
const documentId = formData.get("documentId") as string;
@@ -328,7 +301,18 @@ export default function CrossCheckingResult() {
const [reviewData, setReviewData] = useState<ReviewData | null>(null);
const [activeReviewPointResultId, setActiveReviewPointResultId] = useState<string | null>(null);
const [targetPage, setTargetPage] = useState<number | undefined>(undefined);
const [localScoringProposals, setLocalScoringProposals] = useState<ScoringProposal[]>(scoring_proposals || []); // 本地状态管理scoringProposals
// 同步外部scoring_proposals到本地状态
useEffect(() => {
setLocalScoringProposals(scoring_proposals || []);
}, [scoring_proposals]);
// 处理意见提交成功的回调
const handleOpinionSubmitted = (newProposal: ScoringProposal) => {
setLocalScoringProposals(prev => [...prev, newProposal]);
};
// loader 数据加载出错
useEffect(()=>{
loadingBarService.hide();
@@ -555,7 +539,7 @@ export default function CrossCheckingResult() {
const responseData = checkRes.data as CheckProposalResponse;
const pendingProposals = responseData?.data?.pending_proposals || [];
console.log("pendingProposals", pendingProposals);
// console.log("pendingProposals", pendingProposals);
// 3. 构建模态框消息
let modalMessage: string = '';
@@ -698,9 +682,10 @@ export default function CrossCheckingResult() {
activeReviewPointResultId={activeReviewPointResultId}
onReviewPointSelect={handleReviewPointSelect}
onStatusChange={handleReviewPointStatusChange}
scoringProposals={scoring_proposals as ScoringProposal[]}
scoringProposals={localScoringProposals}
jwtToken={jwtToken}
userInfo={userInfo}
onOpinionSubmitted={handleOpinionSubmitted}
/>
</div>
</div>
+5 -9
View File
@@ -311,14 +311,10 @@ export default function CrossCheckingUpload() {
const isZip = file.type === 'application/zip' ||
file.type === 'application/x-zip-compressed' ||
file.name.toLowerCase().endsWith('.zip');
const isRar = file.type === 'application/x-rar-compressed' ||
file.name.toLowerCase().endsWith('.rar');
const is7z = file.type === 'application/x-7z-compressed' ||
file.name.toLowerCase().endsWith('.7z');
const isTar = file.type === 'application/x-tar' ||
file.name.toLowerCase().endsWith('.tar');
if (isZip || isRar || is7z || isTar) {
if (isZip || is7z) {
validFiles.push({
id: generateFileId(),
file,
@@ -333,7 +329,7 @@ export default function CrossCheckingUpload() {
});
if (hasInvalidFiles) {
messageService.error('只能上传ZIP或RAR格式的压缩文件', {
messageService.error('只能上传ZIP或7Z格式的压缩文件', {
title: '文件类型错误',
confirmText: '确定',
});
@@ -879,14 +875,14 @@ export default function CrossCheckingUpload() {
ref={multipleUploadRef}
onFilesSelected={handleMultipleFilesSelected}
className="custom-upload-area"
accept=".zip,.rar,.7z,.tar"
accept=".zip,.7z"
multiple={false}
icon="ri-folder-zip-line"
buttonText="选择文件"
mainText="点击或拖拽文件到此区域上传"
tipText={
<div className="upload-tip-error">
ziprar7ztar文件
zip7z文件
</div>
}
disabled={uploadType === 'single' || isUploading}
+14 -14
View File
@@ -122,16 +122,16 @@ export async function action({ request }: ActionFunctionArgs) {
// 打印session信息
console.log("=== 测试用户登录 - Session信息 ===");
console.log("保存到session的userInfo:", enhancedUserInfo);
console.log("session数据结构:", {
isAuthenticated: true,
userRole: userRole,
accessToken: "mock_access_token_for_test",
refreshToken: "mock_refresh_token_for_test",
tokenIssuedAt: Date.now(),
tokenExpiresIn: mockTokenExpiresIn,
frontendJWT: frontendJWT,
userInfo: enhancedUserInfo
});
// console.log("session数据结构:", {
// isAuthenticated: true,
// userRole: userRole,
// accessToken: "mock_access_token_for_test",
// refreshToken: "mock_refresh_token_for_test",
// tokenIssuedAt: Date.now(),
// tokenExpiresIn: mockTokenExpiresIn,
// frontendJWT: frontendJWT,
// userInfo: enhancedUserInfo
// });
const cookie = await sessionStorage.commitSession(session);
@@ -184,7 +184,7 @@ export default function Login() {
const handleOAuthLogin = () => {
try {
// 创建OAuth客户端
const oauthClient = new OAuthClient(OAUTH_CONFIG);
const oauthClient = new OAuthClient(OAUTH_CONFIG.value);
// 生成状态值
const state = oauthClient.generateState();
@@ -205,8 +205,8 @@ export default function Login() {
useEffect(() => {
// 检查OAuth配置是否完整
if (!OAUTH_CONFIG.serverUrl || !OAUTH_CONFIG.clientId || !OAUTH_CONFIG.clientSecret) {
console.error("OAuth2.0配置不完整:", OAUTH_CONFIG);
if (!OAUTH_CONFIG.value.serverUrl || !OAUTH_CONFIG.value.clientId || !OAUTH_CONFIG.value.clientSecret) {
console.error("OAuth2.0配置不完整:", OAUTH_CONFIG.value);
}
}, []);
@@ -280,4 +280,4 @@ export default function Login() {
</div>
</div>
);
}
}
+2 -2
View File
@@ -12,7 +12,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
if (accessToken) {
try {
// 创建OAuth客户端
const oauthClient = new OAuthClient(OAUTH_CONFIG);
const oauthClient = new OAuthClient(OAUTH_CONFIG.value);
// 构建登出后重定向URL
const url = new URL(request.url);
@@ -48,4 +48,4 @@ export default function Logout() {
</div>
</div>
);
}
}
+249
View File
@@ -0,0 +1,249 @@
/**
* 客户端配置测试页面
* 用于验证Nginx代理和客户端ID检测是否正常工作
*/
import { json, type LoaderFunctionArgs } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { getApiConfig } from "~/config/api-config";
import { detectClientFromRequest, getRequestDebugInfo } from "~/utils/client-detection";
/**
* 服务器端loader函数 - 获取配置和调试信息
*/
export async function loader({ request }: LoaderFunctionArgs) {
// 获取客户端配置
const config = getApiConfig(request);
// 获取客户端检测信息
const detectedClientId = detectClientFromRequest(request);
// 获取调试信息
const debugInfo = getRequestDebugInfo(request);
// 获取当前URL信息
const url = new URL(request.url);
return json({
config,
detectedClientId,
debugInfo,
serverInfo: {
url: url.href,
host: url.host,
port: url.port,
pathname: url.pathname
},
timestamp: new Date().toISOString()
});
}
/**
* 客户端配置测试页面组件
*/
export default function ClientConfigTest() {
const data = useLoaderData<typeof loader>();
// 浏览器端检测客户端ID
const browserClientId = typeof window !== 'undefined' ? (() => {
const port = window.location.port;
const portToClient: Record<string, string> = {
'5174': 'client-a',
'5175': 'client-b',
'5176': 'client-c',
'5177': 'client-d'
};
return port && portToClient[port] ? portToClient[port] : 'unknown';
})() : 'server-side';
const browserPort = typeof window !== 'undefined' ? window.location.port : 'server-side';
return (
<div className="min-h-screen bg-gray-50 py-8">
<div className="max-w-4xl mx-auto px-4">
<div className="bg-white rounded-lg shadow-lg p-6">
<h1 className="text-3xl font-bold text-gray-900 mb-6">
🧪
</h1>
{/* 基本信息 */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
<div className="bg-blue-50 p-4 rounded-lg">
<h2 className="text-lg font-semibold text-blue-900 mb-3">📍 访</h2>
<div className="space-y-2 text-sm">
<div><strong>URL:</strong> {data.serverInfo.url}</div>
<div><strong>:</strong> {data.serverInfo.host}</div>
<div><strong>:</strong> {data.serverInfo.port || '默认端口'}</div>
<div><strong>:</strong> {data.serverInfo.pathname}</div>
<div><strong>:</strong> {browserPort}</div>
</div>
</div>
<div className="bg-green-50 p-4 rounded-lg">
<h2 className="text-lg font-semibold text-green-900 mb-3">🎯 </h2>
<div className="space-y-2 text-sm">
<div><strong>:</strong>
<span className={`ml-2 px-2 py-1 rounded text-xs ${
data.detectedClientId !== 'main' ? 'bg-green-200 text-green-800' : 'bg-yellow-200 text-yellow-800'
}`}>
{data.detectedClientId}
</span>
</div>
<div><strong>:</strong>
<span className={`ml-2 px-2 py-1 rounded text-xs ${
browserClientId !== 'unknown' ? 'bg-green-200 text-green-800' : 'bg-yellow-200 text-yellow-800'
}`}>
{browserClientId}
</span>
</div>
</div>
</div>
</div>
{/* Nginx请求头信息 */}
<div className="mb-8">
<h2 className="text-lg font-semibold text-gray-900 mb-3">🔍 Nginx请求头信息</h2>
<div className="bg-gray-50 p-4 rounded-lg">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm">
<div>
<strong>X-Client-ID:</strong>
<span className={`ml-2 px-2 py-1 rounded text-xs ${
data.debugInfo.clientId ? 'bg-green-200 text-green-800' : 'bg-red-200 text-red-800'
}`}>
{data.debugInfo.clientId || '未检测到'}
</span>
</div>
<div>
<strong>X-Original-Port:</strong>
<span className={`ml-2 px-2 py-1 rounded text-xs ${
data.debugInfo.originalPort ? 'bg-green-200 text-green-800' : 'bg-red-200 text-red-800'
}`}>
{data.debugInfo.originalPort || '未检测到'}
</span>
</div>
<div>
<strong>X-Forwarded-Port:</strong>
<span className="ml-2 px-2 py-1 rounded text-xs bg-gray-200 text-gray-800">
{data.debugInfo.forwardedPort || '未设置'}
</span>
</div>
<div>
<strong>X-Real-IP:</strong>
<span className="ml-2 px-2 py-1 rounded text-xs bg-gray-200 text-gray-800">
{data.debugInfo.realIp || '未设置'}
</span>
</div>
</div>
</div>
</div>
{/* 当前配置信息 */}
<div className="mb-8">
<h2 className="text-lg font-semibold text-gray-900 mb-3"> API配置</h2>
<div className="bg-gray-50 p-4 rounded-lg">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm">
<div><strong>Base URL:</strong> {data.config.baseUrl}</div>
<div><strong>Upload URL:</strong> {data.config.uploadUrl}</div>
<div><strong>Document URL:</strong> {data.config.documentUrl}</div>
<div><strong>OAuth Server:</strong> {data.config.oauth.serverUrl}</div>
<div><strong>OAuth Redirect:</strong> {data.config.oauth.redirectUri}</div>
<div><strong>OAuth Client ID:</strong> {data.config.oauth.clientId.substring(0, 20)}...</div>
</div>
</div>
</div>
{/* 状态检查 */}
<div className="mb-8">
<h2 className="text-lg font-semibold text-gray-900 mb-3"> </h2>
<div className="space-y-3">
<div className="flex items-center space-x-3">
<div className={`w-3 h-3 rounded-full ${
data.debugInfo.clientId ? 'bg-green-500' : 'bg-red-500'
}`}></div>
<span className={data.debugInfo.clientId ? 'text-green-700' : 'text-red-700'}>
{data.debugInfo.clientId ? '✅ Nginx X-Client-ID 传递正常' : '❌ Nginx X-Client-ID 未传递'}
</span>
</div>
<div className="flex items-center space-x-3">
<div className={`w-3 h-3 rounded-full ${
data.detectedClientId !== 'main' ? 'bg-green-500' : 'bg-yellow-500'
}`}></div>
<span className={data.detectedClientId !== 'main' ? 'text-green-700' : 'text-yellow-700'}>
{data.detectedClientId !== 'main' ? '✅ 服务器端客户端检测成功' : '⚠️ 服务器端使用默认配置'}
</span>
</div>
<div className="flex items-center space-x-3">
<div className={`w-3 h-3 rounded-full ${
browserClientId !== 'unknown' ? 'bg-green-500' : 'bg-yellow-500'
}`}></div>
<span className={browserClientId !== 'unknown' ? 'text-green-700' : 'text-yellow-700'}>
{browserClientId !== 'unknown' ? '✅ 浏览器端客户端检测成功' : '⚠️ 浏览器端使用默认配置'}
</span>
</div>
<div className="flex items-center space-x-3">
<div className={`w-3 h-3 rounded-full ${
data.config.baseUrl.includes(data.detectedClientId.replace('client-', '517')) ? 'bg-green-500' : 'bg-yellow-500'
}`}></div>
<span className={data.config.baseUrl.includes(data.detectedClientId.replace('client-', '517')) ? 'text-green-700' : 'text-yellow-700'}>
{data.config.baseUrl.includes(data.detectedClientId.replace('client-', '517')) ? '✅ 配置匹配正确' : '⚠️ 配置可能不匹配'}
</span>
</div>
</div>
</div>
{/* 调试信息 */}
<div className="mb-8">
<h2 className="text-lg font-semibold text-gray-900 mb-3">🔧 </h2>
<details className="bg-gray-50 p-4 rounded-lg">
<summary className="cursor-pointer text-sm font-medium text-gray-700 mb-2">
</summary>
<pre className="text-xs bg-white p-3 rounded border overflow-auto">
{JSON.stringify({
serverData: data,
browserInfo: {
clientId: browserClientId,
port: browserPort,
userAgent: typeof window !== 'undefined' ? navigator.userAgent : 'server-side'
}
}, null, 2)}
</pre>
</details>
</div>
{/* 测试链接 */}
<div>
<h2 className="text-lg font-semibold text-gray-900 mb-3">🔗 </h2>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
{[
{ port: '5174', client: 'client-a', name: '客户端A' },
{ port: '5175', client: 'client-b', name: '客户端B' },
{ port: '5176', client: 'client-c', name: '客户端C' },
{ port: '5177', client: 'client-d', name: '客户端D' }
].map(({ port, client, name }) => (
<a
key={port}
href={`http://localhost:${port}/test/client-config`}
className={`block p-3 rounded-lg text-center text-sm font-medium transition-colors ${
browserPort === port
? 'bg-blue-600 text-white'
: 'bg-blue-100 text-blue-700 hover:bg-blue-200'
}`}
>
{name}<br/>
<span className="text-xs opacity-75">:{port}</span>
</a>
))}
</div>
</div>
<div className="mt-6 text-xs text-gray-500">
: {data.timestamp}
</div>
</div>
</div>
</div>
);
}