完善grade(统计成绩页),增加删除数据按钮,增加标准答案字典,支持导出标准答案,完善计分方式
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
node_modules
|
node_modules
|
||||||
|
.next/
|
||||||
.next
|
.next
|
||||||
2
.next/cache/.rscinfo
vendored
2
.next/cache/.rscinfo
vendored
@@ -1 +1 @@
|
|||||||
{"encryption.key":"AxmjrhnEIlWTqaOj84DDbm8NSadx1bBdyyMc77kaHFY=","encryption.expire_at":1766739099583}
|
{"encryption.key":"41RLTu25s6bKnxtTa0cAIxEID3ece30//eTx5e3P3aE=","encryption.expire_at":1767849224402}
|
||||||
BIN
.next/cache/webpack/client-development/0.pack.gz
vendored
BIN
.next/cache/webpack/client-development/0.pack.gz
vendored
Binary file not shown.
BIN
.next/cache/webpack/client-development/1.pack.gz
vendored
BIN
.next/cache/webpack/client-development/1.pack.gz
vendored
Binary file not shown.
BIN
.next/cache/webpack/client-development/index.pack.gz
vendored
BIN
.next/cache/webpack/client-development/index.pack.gz
vendored
Binary file not shown.
Binary file not shown.
BIN
.next/cache/webpack/server-development/0.pack.gz
vendored
BIN
.next/cache/webpack/server-development/0.pack.gz
vendored
Binary file not shown.
BIN
.next/cache/webpack/server-development/index.pack.gz
vendored
BIN
.next/cache/webpack/server-development/index.pack.gz
vendored
Binary file not shown.
@@ -2,6 +2,6 @@
|
|||||||
"/_app": "pages/_app.js",
|
"/_app": "pages/_app.js",
|
||||||
"/_error": "pages/_error.js",
|
"/_error": "pages/_error.js",
|
||||||
"/_document": "pages/_document.js",
|
"/_document": "pages/_document.js",
|
||||||
"/": "pages/index.js",
|
"/grade": "pages/grade.js",
|
||||||
"/grade": "pages/grade.js"
|
"/": "pages/index.js"
|
||||||
}
|
}
|
||||||
55
.next/trace
55
.next/trace
File diff suppressed because one or more lines are too long
@@ -162,9 +162,9 @@ export default function Office({
|
|||||||
<div
|
<div
|
||||||
className={`w-16 h-16 mb-1 transition-colors flex items-center justify-center ${getPortStyle(`1A-${i + 1}`)}`}
|
className={`w-16 h-16 mb-1 transition-colors flex items-center justify-center ${getPortStyle(`1A-${i + 1}`)}`}
|
||||||
jstype="testport-copper"
|
jstype="testport-copper"
|
||||||
id={`1A-${i + 1}`}
|
id={`Rack1-1A-${i + 1}`}
|
||||||
onClick={() => onPortClick(`1A-${i + 1}`)}
|
onClick={() => onPortClick(`Rack1-1A-${i + 1}`)}
|
||||||
onMouseEnter={() => onPortHover(`1A-${i + 1}`)}
|
onMouseEnter={() => onPortHover(`Rack1-1A-${i + 1}`)}
|
||||||
>
|
>
|
||||||
<div className={`w-8 h-8 bg-[url('/rj45.png')] bg-contain bg-no-repeat bg-center`}></div>
|
<div className={`w-8 h-8 bg-[url('/rj45.png')] bg-contain bg-no-repeat bg-center`}></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -190,9 +190,9 @@ export default function Office({
|
|||||||
className={`w-16 h-16 mb-1 transition-colors flex items-center justify-center ${getPortStyle(`1B-${i + 1}`)}`}
|
className={`w-16 h-16 mb-1 transition-colors flex items-center justify-center ${getPortStyle(`1B-${i + 1}`)}`}
|
||||||
jstype="testport-fiber"
|
jstype="testport-fiber"
|
||||||
lcclean="false"
|
lcclean="false"
|
||||||
id={`1B-${i + 1}`}
|
id={`Rack1-1B-${i + 1}`}
|
||||||
onClick={() => onPortClick(`1B-${i + 1}`)}
|
onClick={() => onPortClick(`Rack1-1B-${i + 1}`)}
|
||||||
onMouseEnter={() => onPortHover(`1B-${i + 1}`)}
|
onMouseEnter={() => onPortHover(`Rack1-1B-${i + 1}`)}
|
||||||
>
|
>
|
||||||
<div className={`w-8 h-8 bg-[url('/lc.png')] bg-contain bg-no-repeat bg-center`}></div>
|
<div className={`w-8 h-8 bg-[url('/lc.png')] bg-contain bg-no-repeat bg-center`}></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export default function OfficeTask() {
|
|||||||
<Image src="/Office.png" alt="office" fill className="object-contain"/>
|
<Image src="/Office.png" alt="office" fill className="object-contain"/>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-gray-300 leading-relaxed">
|
<p className="text-gray-300 leading-relaxed">
|
||||||
在这些重要链路正式启用前,集团工程部要求进行 <span className="font-semibold text-red-400">全面的物理层认证测试</span>。你的测试部署对于确保每条连接都符合严格的 <span className="font-medium text-yellow-300">GB/T信道链路</span>标准。我们不能容忍任何故障链路。
|
在这些重要链路正式启用前,集团工程部要求进行 <span className="font-semibold text-red-400">全面的物理层认证测试</span>。你的测试部署对于确保每条连接都符合严格的 <span className="font-medium text-yellow-300">GB/T永久链路</span>标准。我们不能容忍任何故障链路。
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ const API_URLS = {
|
|||||||
// 教学相关
|
// 教学相关
|
||||||
TEACHING: {
|
TEACHING: {
|
||||||
DATA: `${BASE_URL}/dsxapi/api/teaching/data`,
|
DATA: `${BASE_URL}/dsxapi/api/teaching/data`,
|
||||||
|
CLEAR: `${BASE_URL}/dsxapi/api/teaching/clear`,
|
||||||
},
|
},
|
||||||
// 管理员鉴权相关
|
// 管理员鉴权相关
|
||||||
ADMIN: {
|
ADMIN: {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { API_URLS } from '@/config/api';
|
import { API_URLS } from '@/config/api';
|
||||||
|
import { faultReferenceDict, resolveFaultReference } from '@/store/faultReferenceDict';
|
||||||
|
|
||||||
|
|
||||||
export default function Grade({ isComponent = false }) {
|
export default function Grade({ isComponent = false }) {
|
||||||
@@ -34,108 +35,209 @@ export default function Grade({ isComponent = false }) {
|
|||||||
|
|
||||||
|
|
||||||
const exportToExcel = () => {
|
const exportToExcel = () => {
|
||||||
import('xlsx').then((xlsx) => {
|
import('xlsx').then(async (xlsx) => {
|
||||||
// 获取表格元素
|
const isValidResult = (result) => {
|
||||||
const table = document.querySelector('table');
|
const mt = result?.testconfig?.moduleType;
|
||||||
if (!table) return;
|
if (mt === '8000') {
|
||||||
|
const lv = result?.testconfig?.params?.limitValue;
|
||||||
// 获取表头数据
|
const ps = (result?.CopperPerformanceStatus || '').toLowerCase();
|
||||||
const headers = [];
|
const ct = result?.testconfig?.params?.cableType;
|
||||||
const headerCells = table.querySelectorAll('thead th');
|
const wo = result?.testconfig?.params?.wireOrder;
|
||||||
headerCells.forEach(cell => {
|
const ref = !!result?.CopperRef;
|
||||||
headers.push(cell.textContent.trim());
|
let limitOk = false;
|
||||||
});
|
if (ps.includes('mptl')) {
|
||||||
|
limitOk = !!(lv?.includes('GBT') && lv?.includes('MPTL') && lv?.includes('PoE'));
|
||||||
// 获取表格数据
|
} else if (ps.includes('workshop')) {
|
||||||
const exportData = [];
|
limitOk = !!lv?.includes('Profinet');
|
||||||
const rows = table.querySelectorAll('tbody tr');
|
|
||||||
rows.forEach(row => {
|
|
||||||
const rowData = {};
|
|
||||||
const cells = row.querySelectorAll('td');
|
|
||||||
cells.forEach((cell, index) => {
|
|
||||||
// 获取单元格中的文本内容
|
|
||||||
let cellText = '';
|
|
||||||
// 查找所有span元素
|
|
||||||
const spans = cell.querySelectorAll('span');
|
|
||||||
if (spans.length > 0) {
|
|
||||||
// 如果有span元素,合并它们的文本内容
|
|
||||||
cellText = Array.from(spans)
|
|
||||||
.map(span => span.textContent.trim())
|
|
||||||
.filter(text => text) // 过滤掉空文本
|
|
||||||
.join(' ');
|
|
||||||
} else {
|
} else {
|
||||||
// 如果没有span元素,直接使用单元格的文本内容
|
limitOk = !!lv?.includes('GBT');
|
||||||
cellText = cell.textContent.trim();
|
|
||||||
}
|
}
|
||||||
rowData[headers[index]] = cellText;
|
const cableOk = ct === 'Cat6 U/UTP';
|
||||||
|
let wireOk = false;
|
||||||
|
if (ps.includes('m12')) {
|
||||||
|
wireOk = !!wo?.includes('M12');
|
||||||
|
} else if (ps.includes('2p')) {
|
||||||
|
wireOk = !!wo?.includes('Ethernet');
|
||||||
|
} else {
|
||||||
|
wireOk = wo === 'T568B';
|
||||||
|
}
|
||||||
|
return limitOk && cableOk && wireOk && ref;
|
||||||
|
}
|
||||||
|
if (mt === 'cfp') {
|
||||||
|
const lv = result?.testconfig?.params?.limitValue;
|
||||||
|
const ct = result?.testconfig?.params?.cableType;
|
||||||
|
const connectorOk = Number(result?.testconfig?.params?.connectorCount) === 2;
|
||||||
|
const spliceOk = Number(result?.testconfig?.params?.spliceCount) === 2;
|
||||||
|
const refOk = result?.CFPRef === true;
|
||||||
|
const refConnOk = result?.CFPRefConnect === true;
|
||||||
|
const cleanOk = result?.PortCleanStatus === 2;
|
||||||
|
const limitOk = !!lv?.includes('GBT');
|
||||||
|
const cableOk = !!ct?.includes('OS2');
|
||||||
|
return limitOk && cableOk && connectorOk && spliceOk && refOk && refConnOk && cleanOk;
|
||||||
|
}
|
||||||
|
if (mt === 'ofp') {
|
||||||
|
const lv = result?.testconfig?.params?.limitValue;
|
||||||
|
const ct = result?.testconfig?.params?.cableType;
|
||||||
|
const refStatusOk = result?.ofpRefStatus === 'start';
|
||||||
|
const refConnOk = result?.OFPRefConnect === true;
|
||||||
|
const cleanOk = result?.PortCleanStatus === 2;
|
||||||
|
const limitOk = !!lv?.includes('GBT');
|
||||||
|
const cableOk = !!ct?.includes('OS2');
|
||||||
|
return limitOk && cableOk && refStatusOk && refConnOk && cleanOk;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
const summary = dataSource.map((row) => {
|
||||||
|
const all = row.projects?.flatMap(p => p.testResults || []) || [];
|
||||||
|
const counted = all.slice(0, 6);
|
||||||
|
const validCount = counted.reduce((n, r) => n + (isValidResult(r) ? 1 : 0), 0);
|
||||||
|
const pass = row.testResultsCount?.passCount || 0;
|
||||||
|
const fail = row.testResultsCount?.failCount || 0;
|
||||||
|
return {
|
||||||
|
场景: scenario,
|
||||||
|
用户: row.userId,
|
||||||
|
项目: row.project || '',
|
||||||
|
测试总数: pass + fail,
|
||||||
|
计分测试条数: counted.length,
|
||||||
|
有效数据条数: validCount,
|
||||||
|
分数: row.score,
|
||||||
|
报告数: Array.isArray(row.reports) ? row.reports.length : 0,
|
||||||
|
最后更新时间: new Date(row.lastUpdate).toLocaleString('zh-CN')
|
||||||
|
};
|
||||||
});
|
});
|
||||||
exportData.push(rowData);
|
const details = [];
|
||||||
|
dataSource.forEach((row) => {
|
||||||
|
const all = row.projects?.flatMap(p => p.testResults || []) || [];
|
||||||
|
const counted = all.slice(0, 6);
|
||||||
|
counted.forEach((r) => {
|
||||||
|
const mt = r?.testconfig?.moduleType || '';
|
||||||
|
const status =
|
||||||
|
mt === '8000' ? r.CopperResultStatus :
|
||||||
|
mt === 'cfp' ? r.CFPResultStatus :
|
||||||
|
mt === 'ofp' ? r.ofpResultStatus : '';
|
||||||
|
details.push({
|
||||||
|
场景: scenario,
|
||||||
|
用户: row.userId,
|
||||||
|
结果名称: r.name || '',
|
||||||
|
模块: mt,
|
||||||
|
有效性: isValidResult(r) ? '有效' : '无效',
|
||||||
|
检测结果: status || '',
|
||||||
|
极限值: r?.testconfig?.params?.limitValue || '',
|
||||||
|
线缆类型: r?.testconfig?.params?.cableType || '',
|
||||||
|
插座配置: r?.testconfig?.params?.wireOrder || '',
|
||||||
|
参考状态: mt === '8000' ? (r?.CopperRef ? '基准已设置' : '基准未设置')
|
||||||
|
: mt === 'cfp' ? (r?.CFPRef ? '参考已设置' : '参考未设置')
|
||||||
|
: mt === 'ofp' ? (r?.ofpRefStatus || '') : ''
|
||||||
});
|
});
|
||||||
|
});
|
||||||
// 创建工作簿并导出
|
});
|
||||||
|
let reference = [];
|
||||||
|
try {
|
||||||
|
const resp = await fetch(API_URLS.CONNECTION.MAP_WITH_SCENE(scenario));
|
||||||
|
if (resp.ok) {
|
||||||
|
const data = await resp.json();
|
||||||
|
reference = Object.entries(data || {})
|
||||||
|
.filter(([id]) => (
|
||||||
|
!id.includes('main-permanent') &&
|
||||||
|
!id.includes('main-channel') &&
|
||||||
|
!id.includes('remote-channel') &&
|
||||||
|
!id.includes('main-cfp-sm-out') &&
|
||||||
|
!id.includes('main-cfp-mm-out') &&
|
||||||
|
!id.includes('main-cfp-in') &&
|
||||||
|
!id.includes('remote-cfp-sm-out') &&
|
||||||
|
!id.includes('remote-cfp-mm-out') &&
|
||||||
|
!id.includes('remote-cfp-in')
|
||||||
|
))
|
||||||
|
.map(([room, conn]) => {
|
||||||
|
const ref = resolveFaultReference(conn);
|
||||||
|
return {
|
||||||
|
场景: scenario,
|
||||||
|
链路名称: `${room}-${conn.connectedTo}`,
|
||||||
|
类型: conn.type === 'fiber' ? '光纤' : '铜缆',
|
||||||
|
故障类型: ref?.type || '',
|
||||||
|
故障位置: ref?.location || '',
|
||||||
|
故障原因: ref?.reason || ''
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
const wb = xlsx.utils.book_new();
|
const wb = xlsx.utils.book_new();
|
||||||
const ws = xlsx.utils.json_to_sheet(exportData);
|
const ws1 = xlsx.utils.json_to_sheet(summary);
|
||||||
xlsx.utils.book_append_sheet(wb, ws, '数据');
|
const ws2 = xlsx.utils.json_to_sheet(details);
|
||||||
|
const ws3 = xlsx.utils.json_to_sheet(reference);
|
||||||
// 生成文件名
|
xlsx.utils.book_append_sheet(wb, ws1, '概览');
|
||||||
|
xlsx.utils.book_append_sheet(wb, ws2, '用户明细');
|
||||||
|
xlsx.utils.book_append_sheet(wb, ws3, '参考答案');
|
||||||
const fileName = isHistoryMode
|
const fileName = isHistoryMode
|
||||||
? `数据_历史_${historyInfo?.uuid || new Date().toISOString().split('T')[0]}.xlsx`
|
? `数据_历史_${historyInfo?.uuid || new Date().toISOString().split('T')[0]}_${scenario}.xlsx`
|
||||||
: `数据_${new Date().toISOString().split('T')[0]}.xlsx`;
|
: `数据_${new Date().toISOString().split('T')[0]}_${scenario}.xlsx`;
|
||||||
|
|
||||||
xlsx.writeFile(wb, fileName);
|
xlsx.writeFile(wb, fileName);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 计算评分的函数
|
|
||||||
const calculateScore = (row) => {
|
const calculateScore = (row) => {
|
||||||
let score = 0;
|
let score = 0;
|
||||||
|
|
||||||
// 项目名称加2分
|
|
||||||
if (row.project === 'Joja-2025') {
|
if (row.project === 'Joja-2025') {
|
||||||
score += 2;
|
score += 10;
|
||||||
}else{
|
}else{
|
||||||
// console.log('项目错误');
|
console.log('项目错误');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 基准设置情况为已设置加2分
|
|
||||||
const allResults = row.projects?.flatMap(project => project.testResults || []) || [];
|
const allResults = row.projects?.flatMap(project => project.testResults || []) || [];
|
||||||
const hasResults = allResults.length > 0;
|
const countedResults = allResults.slice(0, 6);
|
||||||
const allSet = hasResults && allResults.every(result => result.CopperRef);
|
score += countedResults.length * 7.5;
|
||||||
|
const validCount = countedResults.reduce((acc, result) => {
|
||||||
if (allSet) {
|
const moduleType = result?.testconfig?.moduleType;
|
||||||
score += 2;
|
if (moduleType === '8000') {
|
||||||
|
const limitValue = result?.testconfig?.params?.limitValue;
|
||||||
|
const performanceStatus = (result?.CopperPerformanceStatus || '').toLowerCase();
|
||||||
|
const cableType = result?.testconfig?.params?.cableType;
|
||||||
|
const wireOrder = result?.testconfig?.params?.wireOrder;
|
||||||
|
const copperRef = !!result?.CopperRef;
|
||||||
|
let limitValid = false;
|
||||||
|
if (performanceStatus.includes('mptl')) {
|
||||||
|
limitValid = !!(limitValue?.includes('GBT') && limitValue?.includes('MPTL') && limitValue?.includes('PoE'));
|
||||||
|
} else if (performanceStatus.includes('workshop')) {
|
||||||
|
limitValid = !!limitValue?.includes('Profinet');
|
||||||
} else {
|
} else {
|
||||||
// console.log('未设置基准');
|
limitValid = !!limitValue?.includes('GBT');
|
||||||
}
|
}
|
||||||
// 检查测试极限值是否正确
|
const cableTypeValid = cableType === 'Cat6 U/UTP';
|
||||||
const allHaveLimit = hasResults && allResults.every(result =>
|
let wireOrderValid = false;
|
||||||
result.testconfig?.params?.limitValue?.includes('GBT')
|
if (performanceStatus.includes('m12')) {
|
||||||
);
|
wireOrderValid = !!wireOrder?.includes('M12');
|
||||||
if (allHaveLimit) {
|
} else if (performanceStatus.includes('2p')) {
|
||||||
score += 2;
|
wireOrderValid = !!wireOrder?.includes('Ethernet');
|
||||||
} else {
|
} else {
|
||||||
// console.log('未设置测试极限值');
|
wireOrderValid = wireOrder === 'T568B';
|
||||||
}
|
}
|
||||||
|
return acc + (limitValid && cableTypeValid && wireOrderValid && copperRef ? 1 : 0);
|
||||||
// 检查命名方式是否符合xxx-xxx格式
|
|
||||||
const hasValidNaming = allResults.some(result =>
|
|
||||||
/^[^-]+(?:-[^-]+)+$/.test(result?.name || '')
|
|
||||||
);
|
|
||||||
if (hasValidNaming) {
|
|
||||||
score += 2;
|
|
||||||
} else {
|
|
||||||
// console.log('命名方式不符合格式');
|
|
||||||
}
|
}
|
||||||
|
if (moduleType === 'cfp') {
|
||||||
// 检查测试结果总数是否大于6
|
const limitValue = result?.testconfig?.params?.limitValue;
|
||||||
const totalResults = row.testResultsCount.passCount + row.testResultsCount.failCount;
|
const cableType = result?.testconfig?.params?.cableType;
|
||||||
if (totalResults >= 5) {
|
const connectorValid = Number(result?.testconfig?.params?.connectorCount) === 2;
|
||||||
score += 2;
|
const spliceValid = Number(result?.testconfig?.params?.spliceCount) === 2;
|
||||||
} else {
|
const refValid = result?.CFPRef === true;
|
||||||
// console.log('测试结果总数小于5');
|
const refConnectValid = result?.CFPRefConnect === true;
|
||||||
|
const cleanValid = result?.PortCleanStatus === 2;
|
||||||
|
const limitValid = !!limitValue?.includes('GBT');
|
||||||
|
const cableValid = !!cableType?.includes('OS2');
|
||||||
|
return acc + (limitValid && cableValid && connectorValid && spliceValid && refValid && refConnectValid && cleanValid ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
if (moduleType === 'ofp') {
|
||||||
return score;
|
const limitValue = result?.testconfig?.params?.limitValue;
|
||||||
|
const cableType = result?.testconfig?.params?.cableType;
|
||||||
|
const refStatusValid = result?.ofpRefStatus === 'start';
|
||||||
|
const refConnectValid = result?.OFPRefConnect === true;
|
||||||
|
const cleanValid = result?.PortCleanStatus === 2;
|
||||||
|
const limitValid = !!limitValue?.includes('GBT');
|
||||||
|
const cableValid = !!cableType?.includes('OS2');
|
||||||
|
return acc + (limitValid && cableValid && refStatusValid && refConnectValid && cleanValid ? 1 : 0);
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, 0);
|
||||||
|
score += validCount * 7.5;
|
||||||
|
return Math.min(100, score);
|
||||||
};
|
};
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
@@ -220,7 +322,7 @@ export default function Grade({ isComponent = false }) {
|
|||||||
// },
|
// },
|
||||||
|
|
||||||
{
|
{
|
||||||
title: '报告提交',
|
title: '课后实验报告提交',
|
||||||
dataIndex: 'reports',
|
dataIndex: 'reports',
|
||||||
key: 'reports',
|
key: 'reports',
|
||||||
},
|
},
|
||||||
@@ -305,17 +407,87 @@ export default function Grade({ isComponent = false }) {
|
|||||||
|
|
||||||
const [selectedRow, setSelectedRow] = useState(null);
|
const [selectedRow, setSelectedRow] = useState(null);
|
||||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||||
|
const [showReference, setShowReference] = useState(false);
|
||||||
|
const [referenceConnections, setReferenceConnections] = useState([]);
|
||||||
|
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
||||||
|
const [alertVisible, setAlertVisible] = useState(false);
|
||||||
|
const [alertMessage, setAlertMessage] = useState('');
|
||||||
|
const [alertType, setAlertType] = useState('info');
|
||||||
|
const handleClearTeachingData = async () => {
|
||||||
|
try {
|
||||||
|
const resp = await fetch(API_URLS.TEACHING.CLEAR, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
scenario,
|
||||||
|
org: orgFromUrl || ''
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if (resp.ok) {
|
||||||
|
setDataSource([]);
|
||||||
|
setOnlineCount(0);
|
||||||
|
setIsHistoryMode(false);
|
||||||
|
setHistoryInfo(null);
|
||||||
|
setAlertMessage('已清除历史数据');
|
||||||
|
setAlertType('success');
|
||||||
|
setAlertVisible(true);
|
||||||
|
} else {
|
||||||
|
setAlertMessage('删除失败');
|
||||||
|
setAlertType('error');
|
||||||
|
setAlertVisible(true);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
setAlertMessage('删除异常');
|
||||||
|
setAlertType('error');
|
||||||
|
setAlertVisible(true);
|
||||||
|
} finally {
|
||||||
|
setShowDeleteConfirm(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleRowClick = (record) => {
|
const handleRowClick = (record) => {
|
||||||
setSelectedRow(record);
|
setSelectedRow(record);
|
||||||
setIsModalVisible(true);
|
setIsModalVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isModalVisible || !showReference) return;
|
||||||
|
const fetchReference = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(API_URLS.CONNECTION.MAP_WITH_SCENE(scenario));
|
||||||
|
if (!response.ok) return;
|
||||||
|
const data = await response.json();
|
||||||
|
const formatted = Object.entries(data || {})
|
||||||
|
.filter(([portId]) => (
|
||||||
|
!portId.includes('main-permanent') &&
|
||||||
|
!portId.includes('main-channel') &&
|
||||||
|
!portId.includes('remote-channel') &&
|
||||||
|
!portId.includes('main-cfp-sm-out') &&
|
||||||
|
!portId.includes('main-cfp-mm-out') &&
|
||||||
|
!portId.includes('main-cfp-in') &&
|
||||||
|
!portId.includes('remote-cfp-sm-out') &&
|
||||||
|
!portId.includes('remote-cfp-mm-out') &&
|
||||||
|
!portId.includes('remote-cfp-in')
|
||||||
|
))
|
||||||
|
.map(([source, connection]) => ({
|
||||||
|
room: source,
|
||||||
|
rack: connection.connectedTo,
|
||||||
|
meta: connection
|
||||||
|
}));
|
||||||
|
setReferenceConnections(formatted);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('参考答案数据获取失败:', e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchReference();
|
||||||
|
}, [isModalVisible, showReference, scenario]);
|
||||||
// 处理文件上传
|
// 处理文件上传
|
||||||
const handleFileUpload = async (event) => {
|
const handleFileUpload = async (event) => {
|
||||||
const file = event.target.files[0];
|
const file = event.target.files[0];
|
||||||
if (!file || !file.name.endsWith('.est')) {
|
if (!file || !file.name.endsWith('.est')) {
|
||||||
alert('请选择.est文件');
|
setAlertMessage('请选择.est文件');
|
||||||
|
setAlertType('warning');
|
||||||
|
setAlertVisible(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,7 +510,9 @@ export default function Grade({ isComponent = false }) {
|
|||||||
|
|
||||||
const data = jsonData;
|
const data = jsonData;
|
||||||
if (!data.competitionStatus?.statisticsData) {
|
if (!data.competitionStatus?.statisticsData) {
|
||||||
alert('文件格式不正确');
|
setAlertMessage('文件格式不正确');
|
||||||
|
setAlertType('error');
|
||||||
|
setAlertVisible(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -373,7 +547,9 @@ export default function Grade({ isComponent = false }) {
|
|||||||
setOnlineCount(sortedData.length);
|
setOnlineCount(sortedData.length);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('读取文件失败:', error);
|
console.error('读取文件失败:', error);
|
||||||
alert('读取文件失败');
|
setAlertMessage('读取文件失败');
|
||||||
|
setAlertType('error');
|
||||||
|
setAlertVisible(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -386,6 +562,16 @@ export default function Grade({ isComponent = false }) {
|
|||||||
<div className="flex justify-between items-center mb-4">
|
<div className="flex justify-between items-center mb-4">
|
||||||
<h2 className="text-xl font-bold text-[#0ff]">详细信息 - {selectedRow?.userId || ''}</h2>
|
<h2 className="text-xl font-bold text-[#0ff]">详细信息 - {selectedRow?.userId || ''}</h2>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<label className="flex items-center gap-2 text-[#0ff]/80">
|
||||||
|
<span>显示参考答案</span>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={showReference}
|
||||||
|
onChange={(e) => setShowReference(e.target.checked)}
|
||||||
|
className="accent-[#0ff]"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsModalVisible(false)}
|
onClick={() => setIsModalVisible(false)}
|
||||||
className="text-[#0ff] hover:text-[#0ff]/70"
|
className="text-[#0ff] hover:text-[#0ff]/70"
|
||||||
@@ -393,10 +579,11 @@ export default function Grade({ isComponent = false }) {
|
|||||||
×
|
×
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-1 gap-6 overflow-hidden">
|
<div className="flex flex-1 gap-6 overflow-hidden">
|
||||||
{/* 左侧项目列表 */}
|
{/* 左侧项目列表 */}
|
||||||
<div className="w-1/2 overflow-y-auto custom-scrollbar bg-[#0F172A] rounded-lg p-4">
|
<div className={`${showReference ? 'w-1/3' : 'w-1/2'} overflow-y-auto custom-scrollbar bg-[#0F172A] rounded-lg p-4`}>
|
||||||
<div className="text-lg font-bold text-[#0ff] mb-4">测试数据</div>
|
<div className="text-lg font-bold text-[#0ff] mb-4">测试数据</div>
|
||||||
{selectedRow?.projects?.map((project) => (
|
{selectedRow?.projects?.map((project) => (
|
||||||
<div key={project.id} className="mb-6">
|
<div key={project.id} className="mb-6">
|
||||||
@@ -430,28 +617,6 @@ export default function Grade({ isComponent = false }) {
|
|||||||
<span className="text-[#0ff] text-lg font-semibold">{result.name}</span>
|
<span className="text-[#0ff] text-lg font-semibold">{result.name}</span>
|
||||||
{(() => {
|
{(() => {
|
||||||
|
|
||||||
// 命名判断
|
|
||||||
// const validNames = [
|
|
||||||
// 'Room1-TO-1-1F-RackA-1A-1', 'Room1-TO-2-1F-RackA-1A-2',
|
|
||||||
// 'Room3-TO-1-1F-RackA-1A-5', 'Room3-TO-2-1F-RackA-1A-6',
|
|
||||||
// 'Room4-CAM-1F-RackA-1A-12',
|
|
||||||
// '2F-RackA-1A-1-1F-RackA-1B-1', '2F-RackA-1A-2-1F-RackA-1B-2',
|
|
||||||
// '2F-RackA-1A-3-1F-RackA-1B-3', '2F-RackA-1A-4-1F-RackA-1B-4',
|
|
||||||
// '2F-RackA-1A-6-1F-RackA-1B-5', '2F-RackA-1A-5-1F-RackA-1B-6',
|
|
||||||
// 'R-M-A-1-PLC-Rack-1A-1', 'R-M-A-2-PLC-Rack-1A-2',
|
|
||||||
// 'PLC-Rack-1B-1-Data-Rack-1A-1', 'PLC-Rack-1B-2-Data-Rack-1A-2',
|
|
||||||
// '1F-RackA-1A-1-Room1-TO-1', '1F-RackA-1A-2-Room1-TO-2',
|
|
||||||
// '1F-RackA-1A-5-Room3-TO-1', '1F-RackA-1A-6-Room3-TO-2',
|
|
||||||
// '1F-RackA-1A-12-Room4-CAM',
|
|
||||||
// '1F-RackA-1B-1-2F-RackA-1A-1', '1F-RackA-1B-2-2F-RackA-1A-2',
|
|
||||||
// '1F-RackA-1B-3-2F-RackA-1A-3', '1F-RackA-1B-4-2F-RackA-1A-4',
|
|
||||||
// '1F-RackA-1B-5-2F-RackA-1A-6', '1F-RackA-1B-6-2F-RackA-1A-5',
|
|
||||||
// 'PLC-Rack-1A-1-R-M-A-1', 'PLC-Rack-1A-2-R-M-A-2',
|
|
||||||
// 'Data-Rack-1A-1-PLC-Rack-1B-1', 'Data-Rack-1A-2-PLC-Rack-1B-2',
|
|
||||||
// '1F-RackA-1C-1', '1F-RackA-1C-2', '1F-RackA-1C-3', '1F-RackA-1C-4'
|
|
||||||
// ];
|
|
||||||
// const isValidName = validNames.includes(result.name);
|
|
||||||
|
|
||||||
const moduleType = result.testconfig?.moduleType;
|
const moduleType = result.testconfig?.moduleType;
|
||||||
let isValid = false;
|
let isValid = false;
|
||||||
|
|
||||||
@@ -766,7 +931,7 @@ export default function Grade({ isComponent = false }) {
|
|||||||
|
|
||||||
|
|
||||||
{/* 右侧报告列表 */}
|
{/* 右侧报告列表 */}
|
||||||
<div className="w-1/2 overflow-y-auto custom-scrollbar bg-[#0F172A] rounded-lg p-4">
|
<div className={`${showReference ? 'w-1/3' : 'w-1/2'} overflow-y-auto custom-scrollbar bg-[#0F172A] rounded-lg p-4`}>
|
||||||
<div className="text-lg font-bold text-[#0ff] mb-4">报告记录</div>
|
<div className="text-lg font-bold text-[#0ff] mb-4">报告记录</div>
|
||||||
{(!selectedRow?.reports || selectedRow.reports.length === 0) ? (
|
{(!selectedRow?.reports || selectedRow.reports.length === 0) ? (
|
||||||
<div className="text-[#ff6600] text-center mt-8">暂无提交报告</div>
|
<div className="text-[#ff6600] text-center mt-8">暂无提交报告</div>
|
||||||
@@ -806,12 +971,97 @@ export default function Grade({ isComponent = false }) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 右侧参考答案列表 */}
|
||||||
|
{showReference && (
|
||||||
|
<div className="w-1/3 overflow-y-auto custom-scrollbar bg-[#0F172A] rounded-lg p-4">
|
||||||
|
<div className="text-lg font-bold text-[#0ff] mb-4">参考答案</div>
|
||||||
|
{referenceConnections.length === 0 ? (
|
||||||
|
<div className="text-[#ff6600] text-center mt-8">暂无参考数据</div>
|
||||||
|
) : (
|
||||||
|
referenceConnections.map((conn, idx) => {
|
||||||
|
const fault = (() => {
|
||||||
|
const m = conn.meta || {};
|
||||||
|
let statusKey = '';
|
||||||
|
if (m.wiremapstatus !== undefined && m.wiremapstatus !== null && String(m.wiremapstatus).length > 0) {
|
||||||
|
if (String(m.wiremapstatus).toLowerCase() === 'pass') {
|
||||||
|
statusKey = m.performancestatus ? `performancestatus:${m.performancestatus}` : '';
|
||||||
|
} else {
|
||||||
|
statusKey = `wiremapstatus:${m.wiremapstatus}`;
|
||||||
|
}
|
||||||
|
} else if (m.fiberstatus) {
|
||||||
|
statusKey = `fiberstatus:${m.fiberstatus}`;
|
||||||
|
}
|
||||||
|
return statusKey ? faultReferenceDict[statusKey] || null : null;
|
||||||
|
})();
|
||||||
|
return (
|
||||||
|
<div key={idx} className="mb-6 p-4 bg-[#1E293B] rounded-lg border border-[#0ff]/20">
|
||||||
|
<div className="flex justify-between items-center mb-2">
|
||||||
|
<span className="text-[#0ff] font-semibold">链路名称:{`${conn.room}-${conn.rack}`}</span>
|
||||||
|
<span className="text-[#0ff]/70 text-sm">{conn.meta?.type === 'fiber' ? '光纤' : '铜缆'}</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-[#0ff]/90">
|
||||||
|
{fault ? (
|
||||||
|
<div className="mb-2 p-2 rounded-md bg-[#334155] border border-[#0ff]/10">
|
||||||
|
<div className="text-sm">类型: {fault.type}</div>
|
||||||
|
<div className="text-sm">位置: {fault.location}</div>
|
||||||
|
<div className="text-sm">原因: {fault.reason}</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="text-sm text-green-400 mt-2">未匹配到故障参考</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="p-4 md:p-6 bg-[#0F172A] ">
|
<div className="p-4 md:p-6 bg-[#0F172A] ">
|
||||||
|
{alertVisible && (
|
||||||
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||||
|
<div className="bg-[#1E293B] rounded-lg p-6 w-full max-w-md">
|
||||||
|
<div className="text-xl font-bold text-[#0ff] mb-4">提示</div>
|
||||||
|
<div className={`${alertType === 'success' ? 'text-green-400' : alertType === 'error' ? 'text-red-400' : 'text-yellow-300'} mb-6`}>
|
||||||
|
{alertMessage}
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<button
|
||||||
|
onClick={() => setAlertVisible(false)}
|
||||||
|
className="bg-[#0ff]/20 hover:bg-[#0ff]/30 text-[#0ff] px-4 py-2 rounded transition-colors"
|
||||||
|
>
|
||||||
|
确定
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{showDeleteConfirm && (
|
||||||
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||||
|
<div className="bg-[#1E293B] rounded-lg p-6 w-full max-w-md">
|
||||||
|
<div className="text-xl font-bold text-[#0ff] mb-4">确认删除</div>
|
||||||
|
<div className="text-[#0ff]/80 mb-6">此操作将删除历史数据,请确认。</div>
|
||||||
|
<div className="flex justify-end gap-3">
|
||||||
|
<button
|
||||||
|
onClick={() => setShowDeleteConfirm(false)}
|
||||||
|
className="bg-[#0ff]/20 hover:bg-[#0ff]/30 text-[#0ff] px-4 py-2 rounded transition-colors"
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleClearTeachingData}
|
||||||
|
className="bg-red-500/20 hover:bg-red-500/30 text-red-300 px-4 py-2 rounded transition-colors"
|
||||||
|
>
|
||||||
|
确认删除
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="mb-4 md:mb-6 p-4 md:p-6 bg-[#1E293B] rounded-lg shadow-lg border border-[#0ff]/20">
|
<div className="mb-4 md:mb-6 p-4 md:p-6 bg-[#1E293B] rounded-lg shadow-lg border border-[#0ff]/20">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<div>
|
<div>
|
||||||
@@ -845,6 +1095,7 @@ export default function Grade({ isComponent = false }) {
|
|||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
exportToExcel();
|
exportToExcel();
|
||||||
@@ -854,6 +1105,13 @@ export default function Grade({ isComponent = false }) {
|
|||||||
导出至Excel
|
导出至Excel
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => setShowDeleteConfirm(true)}
|
||||||
|
className="bg-red-500/20 hover:bg-red-500/30 text-red-300 px-4 py-2 rounded transition-colors"
|
||||||
|
>
|
||||||
|
删除历史数据
|
||||||
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
54
src/store/faultReferenceDict.js
Normal file
54
src/store/faultReferenceDict.js
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
export const faultReferenceDict = {
|
||||||
|
'wiremapstatus:open': { type: '开路', location: '线芯1距离主机端0米,距离远端21.4米开路', reason: '可能是靠近主机端连接器压接不到位' },
|
||||||
|
'wiremapstatus:open2': { type: '开路', location: '线对4,5距离主机端0.2米,距离远端22.4米开路', reason: '可能是靠近主机端连接器压接不到位' },
|
||||||
|
'wiremapstatus:open3': { type: '开路', location: '线芯2、6、4、5、7、8距离主机端11米,远端13米开路', reason: '可能是被老鼠咬了' },
|
||||||
|
'wiremapstatus:short': { type: '短路', location: '线芯1、2、6距离主机端1.1米处短路', reason: '可能是靠近主机端的位置线缆外护套破损' },
|
||||||
|
'wiremapstatus:short2': { type: '短路', location: '线芯1、6、7距离远端1.1米处短路', reason: '可能是靠近远端的位置线缆外护套破损' },
|
||||||
|
'wiremapstatus:short3': { type: '短路', location: '线芯1、2、4、8距离主机端0.1米处短路', reason: '可能是端接时外护套开剥过深' },
|
||||||
|
'wiremapstatus:cross': { type: '跨接', location: '线对1、2与线对3、6跨接', reason: '端接错误' },
|
||||||
|
'wiremapstatus:cross2': { type: '跨接', location: '线对1、2与线对4、5跨接', reason: '端接错误' },
|
||||||
|
'wiremapstatus:cross3': { type: '跨接', location: '线对3、6与线对4、5跨接', reason: '端接错误' },
|
||||||
|
'wiremapstatus:reversed': { type: '交叉', location: '线对4、5交叉', reason: '端接错误' },
|
||||||
|
'wiremapstatus:reversed2': { type: '交叉', location:'线对1、2交叉', reason: '端接错误' },
|
||||||
|
'wiremapstatus:reversed3': { type: '交叉', location: '线对3、6,线对7、8交叉', reason: '端接错误' },
|
||||||
|
'wiremapstatus:miswire': { type: '乱序', location: '无法定位', reason: '端接错误' },
|
||||||
|
'wiremapstatus:sopen': { type: '屏蔽层开路', location: '无法定位', reason: '未正确接地' },
|
||||||
|
'wiremapstatus:ShieldOpen': { type: '屏蔽层开路', location: '无法定位', reason: '未正确接地' },
|
||||||
|
'wiremapstatus:ShieldShort': { type: '屏蔽层短路', location: '线芯4距离主机端0.1米处短路', reason: '未正确可能是端接时外护套开剥过深' },
|
||||||
|
|
||||||
|
'performancestatus:return-loss-fail': { type: '回波损耗不合格', location: '整条链路', reason: '链路阻抗不连续' },
|
||||||
|
'performancestatus:next-fail': { type: '近端串扰不合格', location: '两端连接器', reason: '两端连接器的串扰过高' },
|
||||||
|
'performancestatus:lengthfail': { type: '长度超限', location: '整条链路', reason: '链路长度超过标准,导致插入损耗过高' },
|
||||||
|
'performancestatus:ohmfail': { type: '电阻类故障', location: '线对1、2', reason: '导体电阻偏高或不均衡' },
|
||||||
|
'performancestatus:mptl-nextfail': { type: '电阻类故障', location: '线对4、5', reason: '线对间电阻差过大' },
|
||||||
|
'performancestatus:pass': { type: '通过', location: '无', reason: '链路通过' },
|
||||||
|
'performancestatus:pass-4m': { type: '通过', location: '无', reason: '链路通过' },
|
||||||
|
'performancestatus:pass-30m': { type: '通过', location: '无', reason: '链路通过' },
|
||||||
|
'performancestatus:workshop-m12-pass-30m': { type: '通过', location: '无', reason: '链路通过' },
|
||||||
|
|
||||||
|
'fiberstatus:bend': { type: '光纤弯曲', location: '在48.95米处出现弯曲', reason: '可能是光纤出现扭曲/弯折' },
|
||||||
|
'fiberstatus:connector-fail-start': { type: '连接器故障', location: '距离主机的第一个连接器', reason: '可能是耦合器质量较差或者连接器损坏/污染' },
|
||||||
|
'fiberstatus:connector-fail-end': { type: '连接器故障', location: '最后一个连接器', reason: '可能是耦合器质量较差或者连接器损坏/污染' },
|
||||||
|
'fiberstatus:splice-fail': { type: '熔接点故障', location: '在12.3米处', reason: '熔接点损耗过大' },
|
||||||
|
'fiberstatus:sm-pass': { type: '单模通过', location: '无', reason: '链路通过' },
|
||||||
|
};
|
||||||
|
|
||||||
|
export const resolveFaultReference = (meta) => {
|
||||||
|
const m = meta || {};
|
||||||
|
const wm = m.wiremapstatus;
|
||||||
|
const pm = m.performancestatus;
|
||||||
|
const fs = m.fiberstatus;
|
||||||
|
let key = '';
|
||||||
|
if (wm !== undefined && wm !== null && String(wm).length > 0) {
|
||||||
|
const wmStr = String(wm).toLowerCase();
|
||||||
|
if (wmStr.startsWith('pass')) {
|
||||||
|
key = pm ? `performancestatus:${pm}` : '';
|
||||||
|
} else {
|
||||||
|
key = `wiremapstatus:${wm}`;
|
||||||
|
}
|
||||||
|
} else if (fs) {
|
||||||
|
key = `fiberstatus:${fs}`;
|
||||||
|
}
|
||||||
|
if (!key) return null;
|
||||||
|
return faultReferenceDict[key] || null;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user