新增grade(成绩统计)页面

This commit is contained in:
2025-12-25 03:34:48 +00:00
parent 37c61f8e72
commit a389879406
32 changed files with 1773 additions and 60 deletions

View File

@@ -27,6 +27,11 @@
"static/chunks/webpack.js",
"static/chunks/main.js",
"static/chunks/pages/_error.js"
],
"/grade": [
"static/chunks/webpack.js",
"static/chunks/main.js",
"static/chunks/pages/grade.js"
]
},
"ampFirstPages": []

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -4,5 +4,11 @@
"files": [
"static/chunks/_pages-dir-browser_node_modules_jsplumb_dist_js_jsplumb_js.js"
]
},
"pages/grade.js -> xlsx": {
"id": "pages/grade.js -> xlsx",
"files": [
"static/chunks/_pages-dir-browser_node_modules_xlsx_xlsx_mjs.js"
]
}
}

View File

@@ -24,6 +24,11 @@ globalThis.__BUILD_MANIFEST = {
"static/chunks/webpack.js",
"static/chunks/main.js",
"static/chunks/pages/_error.js"
],
"/grade": [
"static/chunks/webpack.js",
"static/chunks/main.js",
"static/chunks/pages/grade.js"
]
},
"ampFirstPages": []

View File

@@ -1 +1 @@
self.__REACT_LOADABLE_MANIFEST="{\"components/JsPlumbInit.js -> jsplumb\":{\"id\":\"components/JsPlumbInit.js -> jsplumb\",\"files\":[\"static/chunks/_pages-dir-browser_node_modules_jsplumb_dist_js_jsplumb_js.js\"]}}"
self.__REACT_LOADABLE_MANIFEST="{\"components/JsPlumbInit.js -> jsplumb\":{\"id\":\"components/JsPlumbInit.js -> jsplumb\",\"files\":[\"static/chunks/_pages-dir-browser_node_modules_jsplumb_dist_js_jsplumb_js.js\"]},\"pages/grade.js -> xlsx\":{\"id\":\"pages/grade.js -> xlsx\",\"files\":[\"static/chunks/_pages-dir-browser_node_modules_xlsx_xlsx_mjs.js\"]}}"

View File

@@ -2,5 +2,6 @@
"/_app": "pages/_app.js",
"/_error": "pages/_error.js",
"/_document": "pages/_document.js",
"/": "pages/index.js"
"/": "pages/index.js",
"/grade": "pages/grade.js"
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

BIN
public/Industry.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 210 KiB

After

Width:  |  Height:  |  Size: 109 KiB

View File

@@ -338,7 +338,7 @@ export default function Cursors() {
setTimeout(() => {
setToneStatus(null);
}, 1200);
console.log(isConnected);
// console.log(isConnected);
}
}
});

View File

@@ -1,5 +1,7 @@
import React, { useState } from 'react';
import useDeviceStore from '@/store/deviceStore';
import useDisplayStore from '@/store/displayStore';
import { API_URLS } from '@/config/api';
const ResultTable = () => {
const [records, setRecords] = useState([
@@ -92,8 +94,68 @@ const ResultTable = () => {
};
const { getCurrentProject,getCurrentOperator, getCurrentTestConfig} = useDisplayStore();
// 提交表单
const handleSubmit = () => {
const { faultScenarios, seatNumber, seatOrg,setTotalToastMessage } = useDeviceStore();
// 获取测试结果统计
const getTestResults = () => {
const currentProject = getCurrentProject();
if (!currentProject?.testResults) return { passCount: 0, failCount: 0 };
const passCount = currentProject?.testResults.filter(result => {
const copperStatus = result.CopperResultStatus;
const cfpStatus = result.CFPResultStatus;
const ofpStatus = result.ofpResultStatus;
if (copperStatus) {
return copperStatus === 'pass';
} else if (cfpStatus) {
return cfpStatus === 'pass';
} else if (ofpStatus) {
return ofpStatus === 'pass';
}
return false;
}).length;
const failCount = currentProject?.testResults.filter(result => {
const copperStatus = result.CopperResultStatus;
const cfpStatus = result.CFPResultStatus;
const ofpStatus = result.ofpResultStatus;
if (copperStatus) {
return copperStatus === 'fail';
} else if (cfpStatus) {
return cfpStatus === 'fail';
} else if (ofpStatus) {
return ofpStatus === 'fail';
}
return false;
}).length;
return { passCount, failCount };
};
// 获取基准状态
const getRefStatus = () => {
const currentConfig = getCurrentTestConfig();
const moduleType = currentConfig?.moduleType;
if (!moduleType) return false;
const { ref } = useDisplayStore.getState();
if (moduleType === '8000') return ref.copper?.status || false;
if (moduleType === 'cfp') return ref.cfp?.status || false;
if (moduleType === 'ofp') return ref.ofp?.status || false;
return false;
};
const currentProject = getCurrentProject();
const currentOperator = getCurrentOperator();
const currentConfig = getCurrentTestConfig();
const refStatus = getRefStatus();
const { passCount, failCount } = getTestResults();
const handleSubmit = async () => {
// 验证表单
const isValid = records.every(record => {
if (!record.linkName.trim()) return false;
@@ -112,9 +174,52 @@ const ResultTable = () => {
// 直接使用当前的 records 更新 store
useDeviceStore.getState().updateReports(records);
alert('记录提交成功!');
try {
const statisticsData = {
userId: seatNumber,
org: seatOrg,
scenario: faultScenarios,
project: currentProject?.name || '',
operator: currentOperator?.name || '',
testLimit: currentConfig?.params?.limitValue || '',
refStatus,
testResultsCount: { passCount, failCount },
projects: useDisplayStore.getState().projects,
selectedIndexes:useDisplayStore.getState().selectedIndexes,
reports: records,
};
if (['Office', 'Industry', 'DataCenter'].includes(faultScenarios)) {
const response = await fetch(API_URLS.TEACHING.DATA, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
scenario: faultScenarios,
fingerprint: seatNumber,
data: statisticsData,
timestamp: Date.now(),
}),
});
if (!response.ok) {
try {
const err = await response.json();
console.error('数据发送失败:', err);
} catch {
console.error('数据发送失败');
}
} else {
setTotalToastMessage('记录提交成功!');
}
}
} catch (e) {
console.error('数据发送异常:', e);
}
};
return (
<div className="bg-[#1E293B] rounded-lg p-6 text-white">
<div className="flex justify-between items-center mb-6">

View File

@@ -136,8 +136,8 @@ export default function Result() {
}
const newResults = testResults.map(result => {
console.log(testResults);
console.log(result);
// console.log(testResults);
// console.log(result);
if (result.name === selectedResults[0]) {
// 处理CFP模块类型的特殊重命名逻辑
@@ -149,7 +149,7 @@ export default function Result() {
if (oldName === result.inputname) {
// 查找关联结果inputname相同但name不同的结果
const relatedResult = testResults.find(r => r.inputname === result.inputname && r.name !== result.name);
console.log("找到的关联结果:", relatedResult);
// console.log("找到的关联结果:", relatedResult);
// 如果是当前选中的结果更新name和inputname
if (result.name === selectedResults[0]) {
// 更新选中结果的name和inputname

View File

@@ -229,7 +229,7 @@ export default function Testing() {
wireOrder === 'Ethernet Two-Pair' ||
wireOrder === 'M12-D Two-Pair') {
CopperWiremapResultStatus = 'pass';
console.log(CopperWiremapResultStatus);
// console.log(CopperWiremapResultStatus);
}
} else if (CopperWiremapStatus === 'pass-2pair') {
if (wireOrder === 'Ethernet Two-Pair' ||
@@ -305,7 +305,7 @@ export default function Testing() {
for (const pair of loopPairs) {
if (data.performance.OHM.LOOP[pair] > limitdata.LOOP) {
hasNegativeMargin = true;
console.log("电阻fail")
// console.log("电阻fail")
break;
}
}
@@ -322,7 +322,7 @@ export default function Testing() {
for (const pair of pairUblPairs) {
if (Math.abs(data.performance.OHM.PAIRUBL[pair]) > limitdata.PAIRUBL) {
hasNegativeMargin = true;
console.log("UBL fail")
// console.log("UBL fail")
break;
}
}
@@ -334,7 +334,7 @@ export default function Testing() {
for (const pair of p2pUblPairs) {
if (Math.abs(data.performance.OHM.P2PUBL[pair]) > limitdata.P2PUBL) {
hasNegativeMargin = true;
console.log("P2P fail")
// console.log("P2P fail")
break;
}
}

View File

@@ -57,7 +57,7 @@ export default function Tools() {
const handleVfl = () => {
fetchConnectionMap();
if (!mainVFLEnd) return;
console.log(connectionStatus?.mainPaths?.['main-vfl']);
// console.log(connectionStatus?.mainPaths?.['main-vfl']);
// 在connectionMap中查找与mainVFLEnd匹配的连接
let connectedPort = null;

View File

@@ -358,7 +358,7 @@ export default function CopperPerformance( ) {
const renderFooter = () => {
const showHDTDR = estmodel === 'general' && testResult?.CopperResultStatus === 'fail' && view === 'DRAW' && curtitle === '回波损耗';
const showHDTDX = estmodel === 'general' && testResult?.CopperResultStatus === 'fail' && view === 'DRAW' && curtitle === 'NEXT';
console.log(estmodel, CopperResultStatus, view, curtitle, showHDTDR, showHDTDX);
// console.log(estmodel, CopperResultStatus, view, curtitle, showHDTDR, showHDTDX);
return (
<div className="h-[60px] bg-[#132843] flex items-center justify-end px-8 gap-4">
{showHDTDR && (

View File

@@ -136,8 +136,8 @@ export default function Result() {
}
const newResults = testResults.map(result => {
console.log(testResults);
console.log(result);
// console.log(testResults);
// console.log(result);
if (result.name === selectedResults[0]) {
// 处理CFP模块类型的特殊重命名逻辑
@@ -149,7 +149,7 @@ export default function Result() {
if (oldName === result.inputname) {
// 查找关联结果inputname相同但name不同的结果
const relatedResult = testResults.find(r => r.inputname === result.inputname && r.name !== result.name);
console.log("找到的关联结果:", relatedResult);
// console.log("找到的关联结果:", relatedResult);
// 如果是当前选中的结果更新name和inputname
if (result.name === selectedResults[0]) {
// 更新选中结果的name和inputname

View File

@@ -229,7 +229,7 @@ export default function Testing() {
wireOrder === 'Ethernet Two-Pair' ||
wireOrder === 'M12-D Two-Pair') {
CopperWiremapResultStatus = 'pass';
console.log(CopperWiremapResultStatus);
// console.log(CopperWiremapResultStatus);
}
} else if (CopperWiremapStatus === 'pass-2pair') {
if (wireOrder === 'Ethernet Two-Pair' ||
@@ -305,7 +305,7 @@ export default function Testing() {
for (const pair of loopPairs) {
if (data.performance.OHM.LOOP[pair] > limitdata.LOOP) {
hasNegativeMargin = true;
console.log("电阻fail")
// console.log("电阻fail")
break;
}
}
@@ -322,7 +322,7 @@ export default function Testing() {
for (const pair of pairUblPairs) {
if (Math.abs(data.performance.OHM.PAIRUBL[pair]) > limitdata.PAIRUBL) {
hasNegativeMargin = true;
console.log("UBL fail")
// console.log("UBL fail")
break;
}
}
@@ -334,7 +334,7 @@ export default function Testing() {
for (const pair of p2pUblPairs) {
if (Math.abs(data.performance.OHM.P2PUBL[pair]) > limitdata.P2PUBL) {
hasNegativeMargin = true;
console.log("P2P fail")
// console.log("P2P fail")
break;
}
}

View File

@@ -57,7 +57,7 @@ export default function Tools() {
const handleVfl = () => {
fetchConnectionMap();
if (!mainVFLEnd) return;
console.log(connectionStatus?.mainPaths?.['main-vfl']);
// console.log(connectionStatus?.mainPaths?.['main-vfl']);
// 在connectionMap中查找与mainVFLEnd匹配的连接
let connectedPort = null;

View File

@@ -83,9 +83,14 @@ export default function IndustryTask() {
<li>
<strong className="text-gray-100">测试准备</strong> 使<span className="font-medium text-yellow-300">DSX </span>
</li>
<li>
<strong className="text-gray-100">认证测试</strong> <span className="font-medium text-yellow-300">GB/T </span><span className="font-medium text-yellow-300">(Crosstalk)</span>
</li>
{/*
<li>
<strong className="text-gray-100">认证测试:</strong> 执行<span className="font-medium text-yellow-300">TIA 1005 </span>标准测试,重点关注<span className="font-medium text-yellow-300">串扰(Crosstalk)</span>和<span className="font-medium text-yellow-300">电磁干扰(EMI)抗性至少需要保证E2等级</span>。
</li>
*/}
<li>
<strong className="text-gray-100">数据分析</strong> <span className="font-semibold text-green-400">(PASS)</span><span className="font-semibold text-red-400">(FAIL*)</span>
</li>

View File

@@ -58,7 +58,7 @@ export default function OfficeTask() {
<p className="text-sm font-semibold text-cyan-400 uppercase tracking-wider">新任务单</p>
<h1 className="text-2xl md:text-3xl font-bold text-gray-100 mt-1">任务要求网络链路认证</h1>
<p className="text-xs text-gray-400">任务编号: SIM-TRN-NTW-005 </p>
<p className="text-xs text-gray-400">地点: Innovate Solutions 公司 - 8 层西翼</p>
<p className="text-xs text-gray-400">地点: Joja 公司 - 8 层西翼</p>
</div>
{/* 情况说明 (背景) */}
@@ -66,13 +66,13 @@ export default function OfficeTask() {
<h2 className="text-xl font-semibold text-cyan-300">情况说明</h2>
<p className="text-gray-300 leading-relaxed">
Innovate Solutions 公司刚刚完成了对其 8 层西翼办公区<span className="font-medium text-yellow-300">Room1 Room4</span> <span className="font-medium text-yellow-300"> 1 (Rack1)</span>
Joja 公司刚刚完成了对其 8 层西翼办公区<span className="font-medium text-yellow-300">Room1 Room4</span> <span className="font-medium text-yellow-300"> 1 (Rack1)</span>
</p>
<div className=" h-100 relative">
<Image src="/Office.png" alt="office" fill className="object-contain"/>
</div>
<p className="text-gray-300 leading-relaxed">
在这些重要链路正式启用前集团工程部要求进行 <span className="font-semibold text-red-400">全面的物理层认证测试</span> <span className="font-medium text-yellow-300">TIA</span>
在这些重要链路正式启用前集团工程部要求进行 <span className="font-semibold text-red-400">全面的物理层认证测试</span> <span className="font-medium text-yellow-300">GB/T</span>
</p>
</div>
@@ -86,6 +86,9 @@ export default function OfficeTask() {
<li>
<strong className="text-gray-100">部署测试设备</strong> 使 <span className="font-medium text-yellow-300">DSX 线</span>
</li>
<li>
<strong className="text-gray-100">创建测试项目</strong> <span className="font-medium text-yellow-300">Joja-8F</span>
</li>
<li>
<strong className="text-gray-100">执行认证测试</strong> <span className="font-medium text-yellow-300"></span> <span className="font-medium text-yellow-300">T568B</span> 线 <span className="font-medium text-yellow-300">Cat 6 (F/UTP)</span> 线
</li>
@@ -99,7 +102,7 @@ export default function OfficeTask() {
<strong className="text-gray-100">诊断故障点</strong> (<span className='text-red-400 font-bold'>FAIL*</span>)使,
</li>
<li>
<strong className="text-gray-100">记录与报告</strong> 使<code className="bg-gray-700 px-1.5 py-0.5 rounded text-xs text-yellow-300">[]-[]</code> (<code className="bg-gray-700 px-1.5 py-0.5 rounded text-xs text-yellow-300">Room1-TO-1-Rack1-1A-1</code>)
<strong className="text-gray-100">记录与报告</strong> 使<code className="bg-gray-700 px-1.5 py-0.5 rounded text-xs text-yellow-300">[]-[]</code> (<code className="bg-gray-700 px-1.5 py-0.5 rounded text-xs text-yellow-300">Room1-TO-1-Rack1-1A-1</code>)
</li>
</ul>
</div>

View File

@@ -23,6 +23,10 @@ const API_URLS = {
START: `${BASE_URL}/dsxapi/api/competition/start`,
END: `${BASE_URL}/dsxapi/api/competition/end`,
},
// 教学相关
TEACHING: {
DATA: `${BASE_URL}/dsxapi/api/teaching/data`,
},
// 管理员鉴权相关
ADMIN: {
VERIFY: `${BASE_URL}/verify-admin`,

View File

@@ -3,6 +3,7 @@ import Office from '@/components/scene/Office';
import Industry from '@/components/scene/Industry';
import WorldSkill from '@/components/scene/WorldSkill';
import CompetitionPage from './competition';
import Grade from './grade';
import { API_URLS } from '@/config/api';
import { useRouter } from 'next/router';
@@ -18,6 +19,7 @@ export default function AdminPage() {
const [availableScenes, setAvailableScenes] = useState(['Office', 'Industry', 'WorldSkill']);
const [showModal, setShowModal] = useState(false);
const [showhistoryModal, setShowhistoryModal] = useState(false);
const [showgradeModal, setShowgradeModal] = useState(false);
const [uuid, setUuid] = useState(null);
const [showConfirmDialog, setShowConfirmDialog] = useState(false);
const [confirmDialogMessage, setConfirmDialogMessage] = useState('');
@@ -420,6 +422,25 @@ export default function AdminPage() {
</div>
)}
{/* 成绩模态框 */}
{showgradeModal && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="w-full h-full">
<div className='absolute top-26 right-12 z-50' >
<button
onClick={async () => {
setShowgradeModal(false)
}}
className="cursor-pointer bg-[#0ff]/20 hover:bg-[#0ff]/30 text-[#0ff] px-4 py-2 rounded transition-colors"
>
返回控制页面
</button>
</div>
<Grade isComponent={false}/>
</div>
</div>
)}
<div className="max-w-8xl mx-auto">
<div className="flex justify-between items-center mb-6">
@@ -469,6 +490,16 @@ export default function AdminPage() {
</button>
</>
)}
{/* <button
onClick={async () => {
setShowgradeModal(true)
}}
className="px-4 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600 transition-colors"
>
过程评价
</button> */}
<button
onClick={handleInit}
disabled={loading}

1540
src/pages/grade.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -84,6 +84,8 @@ export default function Home() {
WorldSkillScenarios,
seatNumber,
updateSeatNumber,
seatOrg,
updateSeatOrg,
seatUUID,
updateSeatUUID,
seatStartTime,
@@ -145,6 +147,7 @@ useEffect(() => {
const urlParams = new URLSearchParams(currentUrl.split('?')[1] || '');
let faultScenariosParam = urlParams.get('faultScenarios') || '';
let estmodelParam = urlParams.get('estmodel') || '';
let org = urlParams.get('org') || '';
try {
faultScenariosParam = decodeURIComponent(faultScenariosParam);
@@ -163,6 +166,9 @@ useEffect(() => {
if (estmodelParam) {
updateEstmodel(estmodelParam);
}
if (org) {
updateSeatOrg(org);
}
} catch (error) {
// 鉴权失败或解析异常:阻止访问
document.body.innerHTML =
@@ -198,13 +204,14 @@ useEffect(() => {
}
const data = await response.json();
console.log('data:', data);
const username = data?.username;
if (!username) {
throw new Error('认证成功但未返回用户名');
}
// 鉴权成功:更新赛位号
updateSeatNumber(username);
// 更新组织机构
setIsFromUrl(true)
} catch (error) {
// 鉴权失败或解析异常:阻止访问
@@ -217,7 +224,6 @@ useEffect(() => {
}, []);
// 工具函数:合并类名,过滤掉假值
function classNames(...classes) {
return classes.filter(Boolean).join(' ');
@@ -639,7 +645,7 @@ function classNames(...classes) {
</button>
{/* 右侧按钮 */}
{/* {faultScenarios !== 'WorldSkill' && ( */}
{faultScenarios !== 'CopperAnalyzer' && (
<button
onClick={() => setIsRightDrawerOpen(!isRightDrawerOpen)}
@@ -656,7 +662,7 @@ function classNames(...classes) {
</div>
</button>
{/* )} */}
)}
{/* 左侧抽屉模态框 */}

View File

@@ -264,7 +264,10 @@ devtools(
// faultScenarios: "CopperAnalyzer",
WorldSkillScenarios: "OFFICE",
// 赛位号
// seatNumber: "husky",
seatNumber: "",
// 组织机构
seatOrg: "",
// 比赛ID
seatUUID: null,
// 比赛开始时间
@@ -497,7 +500,12 @@ devtools(
seatNumber: number
}));
},
// 更新组织机构
updateSeatOrg: (org) => {
set(() => ({
seatOrg: org
}));
},
// 更新比赛ID
updateSeatUUID: (uuid) => {
set(() => ({