This commit is contained in:
2025-09-16 16:39:48 +08:00
commit c5808e85e2
336 changed files with 695951 additions and 0 deletions

View File

@@ -0,0 +1,274 @@
import React, { useState, useEffect } from 'react';
import StatusBar from '../lib/StatusBar';
import TitleBar from '../lib/TitleBar';
import Keyboard from '../lib/Keyboard';
import useDisplayStore from '@/store/displayStore';
export default function CableId() {
const [showKeyboard, setShowKeyboard] = useState(true);
const [cursorPosition, setCursorPosition] = useState(0);
const [inputValue, setInputValue] = useState('');
const [inputValue2, setInputValue2] = useState('');
const [activeInput, setActiveInput] = useState(1); // 1 表示第一个输入框2 表示第二个输入框
const {
getCurrentProject,
getCurrentCableId,
getCurrentCableId2,
updateProject,
goBack
} = useDisplayStore();
const currentProject = getCurrentProject();
// 获取当前选中的电缆ID
const currentCableId = getCurrentCableId().name;
// 获取当前选中的电缆ID2
const currentCableId2 = getCurrentCableId2().name;
const { view } = useDisplayStore.getState().navigation.current;
// 只在cfp视图下初始化第二个输入框
useEffect(() => {
setInputValue(currentCableId);
if (view === 'cfp') {
setInputValue2(currentCableId2);
}
}, [currentCableId, currentCableId2, view]);
// 计算下一个序号的ID
const getNextId = (currentId) => {
if (!currentId) return '';
// 检查是否以数字结尾
const numMatch = currentId.match(/^(.*?)(\d+)$/);
if (numMatch) {
const [, prefix, num] = numMatch;
const nextNum = String(Number(num) + 1).padStart(num.length, '0');
return prefix + nextNum;
}
// 检查是否以字母结尾
const letterMatch = currentId.match(/^(.*?)([a-zA-Z]+)$/);
if (letterMatch) {
const [, prefix, letters] = letterMatch;
// 将字母转换为数组以便处理
const letterArray = letters.split('');
let carry = true;
// 从右向左处理每个字母
for (let i = letterArray.length - 1; i >= 0 && carry; i--) {
if (letterArray[i] === 'z') {
letterArray[i] = 'a';
carry = true;
} else if (letterArray[i] === 'Z') {
letterArray[i] = 'A';
carry = true;
} else {
letterArray[i] = String.fromCharCode(letterArray[i].charCodeAt(0) + 1);
carry = false;
}
}
// 如果还有进位,说明需要在前面添加一个字母
if (carry) {
if (letters[0] >= 'a' && letters[0] <= 'z') {
letterArray.unshift('a');
} else {
letterArray.unshift('A');
}
}
return prefix + letterArray.join('');
}
// 如果既不是数字也不是字母结尾,直接返回原值
return currentId;
};
// 只在cfp视图下自动更新第二个输入框
useEffect(() => {
if (view === 'cfp' && inputValue) {
setInputValue2(getNextId(inputValue));
}
}, [inputValue, view]);
const handleComplete = () => {
if (view === 'main' && !inputValue.trim()) return;
if (view === 'cfp' && (!inputValue.trim() || !inputValue2.trim())) return;
const currentIndex = useDisplayStore.getState().selectedIndexes.projectIndex;
const currentCableIds = currentProject?.cableIds || [];
let cableIds;
if (view === 'main') {
// 在main视图下只更新选中的ID保留其他ID
const selectedId = getCurrentCableId().id;
cableIds = currentCableIds.map(cable =>
cable.id === selectedId ? { ...cable, name: inputValue.trim() } : cable
);
} else {
cableIds = [
{ id: '1', name: inputValue.trim() },
{ id: '2', name: inputValue2.trim() }
];
}
updateProject(currentIndex, { cableIds });
goBack();
};
const renderContent = () => {
switch (view) {
case 'main':
return (
<div className="flex-1 bg-[#303040] p-4 flex flex-col">
<div
className="relative mb-4 cursor-pointer"
onClick={() => setShowKeyboard(true)}
>
<input
type="text"
className="w-full h-[50px] bg-[#ffffe1] rounded-sm p-4 text-black"
value={inputValue}
placeholder="请输入线缆ID"
onChange={(e) => {
setInputValue(e.target.value);
setCursorPosition(e.target.selectionStart);
}}
onClick={(e) => {
setShowKeyboard(true);
setCursorPosition(e.target.selectionStart);
}}
onFocus={(e) => {
// 保存光标位置
const cursorPosition = e.target.selectionStart;
e.target.setSelectionRange(cursorPosition, cursorPosition);
}}
/>
</div>
{showKeyboard && (
<Keyboard
value={inputValue}
cursorPosition={cursorPosition}
onChange={(newValue, newPosition) => {
setInputValue(newValue);
setCursorPosition(newPosition);
}}
onComplete={() => {
setShowKeyboard(false);
}}
/>
)}
</div>
);
case 'cfp':
return (
<div className="flex-1 bg-[#303040] p-4 flex flex-col">
<div className="mb-8">
<div className="mb-1 text-white text-sm">输出光纤ID1</div>
<div className="relative cursor-pointer">
<input
type="text"
className="w-full h-[50px] bg-[#ffffe1] rounded-sm px-4 text-black overflow-x-auto whitespace-nowrap"
value={inputValue}
placeholder="请输入线缆ID1"
onChange={(e) => {
setInputValue(e.target.value);
setCursorPosition(e.target.selectionStart);
}}
onClick={(e) => {
setActiveInput(1);
setShowKeyboard(true);
setCursorPosition(e.target.selectionStart);
}}
onFocus={(e) => {
const cursorPosition = e.target.selectionStart;
e.target.setSelectionRange(cursorPosition, cursorPosition);
}}
/>
</div>
</div>
<div>
<div className="mb-1 text-white text-sm">输入光纤ID2</div>
<div className="relative cursor-pointer">
<input
type="text"
className="w-full h-[50px] bg-[#ffffe1] rounded-sm px-4 text-black overflow-x-auto whitespace-nowrap"
value={inputValue2}
placeholder="请输入线缆ID2"
onChange={(e) => {
setInputValue2(e.target.value);
setCursorPosition(e.target.selectionStart);
}}
onClick={(e) => {
setActiveInput(2);
setShowKeyboard(true);
setCursorPosition(e.target.selectionStart);
}}
onFocus={(e) => {
const cursorPosition = e.target.selectionStart;
e.target.setSelectionRange(cursorPosition, cursorPosition);
}}
/>
</div>
</div>
{showKeyboard && (
<Keyboard
value={activeInput === 1 ? inputValue : inputValue2}
cursorPosition={cursorPosition}
onChange={(newValue, newPosition) => {
if (activeInput === 1) {
setInputValue(newValue);
} else {
setInputValue2(newValue);
}
setCursorPosition(newPosition);
}}
onComplete={() => {
setShowKeyboard(false);
}}
/>
)}
</div>
);
default:
return null;
}
};
return (
<div className="w-full h-full flex flex-col overflow-hidden">
<StatusBar />
<TitleBar
title="更改ID"
backTo={useDisplayStore.getState().navigation.previous?.name || 'home'}
view={useDisplayStore.getState().navigation.previous?.view || 'main'}
/>
{renderContent()}
{view === 'main' && (
<div className="h-[60px] bg-[#303030] flex items-center justify-end px-8">
{!showKeyboard && (<button
onClick={handleComplete}
className="w-[100px] h-[40px] bg-gradient-to-b from-[#656565] to-[#313431] rounded-sm flex items-center justify-center text-white font-bold shadow-lg"
>
完成
</button>)}
</div>
)}
{view === 'cfp' && (
<div className="h-[60px] bg-[#303030] flex items-center justify-end px-8">
{!showKeyboard && (<button
onClick={handleComplete}
className="w-[100px] h-[40px] bg-gradient-to-b from-[#656565] to-[#313431] rounded-sm flex items-center justify-center text-white font-bold shadow-lg"
>
完成
</button>)}
</div>
)}
</div>
);
}

View File

@@ -0,0 +1,349 @@
import React, { useState, useEffect } from 'react';
import StatusBar from '../lib/StatusBar';
import ResultTitleBar from '../lib/ResultTitleBar';
import useDisplayStore from '@/store/displayStore';
import FrequencyChart from '../lib/FrequencyChart';
import HDTDChart from '../lib/HDTDChart';
export default function CopperPerformance( ) {
const { navigation, navigateTo,goBack } = useDisplayStore();
const { view } = navigation.current;
const curtitle = navigation.current.params.curtitle;
const testResult = navigation.current.params.testResult;
const limitValue = testResult.testconfig.params.limitValue;
const [limitdata, setLimitdata] = useState(null);
const wireOrder = testResult?.testconfig?.params?.wireOrder;
useEffect(() => {
// 异步加载限制数据
const loadLimitData = async () => {
if (limitValue) {
try {
// 从limitValue中提取基础名称(移除+PoE和+ALL后缀)
const baseName = limitValue.split(' (+')[0];
// 使用基础名称加载带有(+ALL)后缀的文件
const data = await import(`@/store/COPPER/${baseName} (+ALL).json`);
setLimitdata(data);
} catch (error) {
console.error('Error loading limit data:', error);
}
}
};
loadLimitData();
}, [limitValue]);
const [poeTab, setPoeTab] = useState('回路');
const renderResultTitleBar = () => {
const handleBack = () => {
goBack();
};
let title = '';
switch (view) {
case 'LENGTH':
title = '长度';
break;
case 'OHM':
title = '电阻';
break;
case 'PoE':
title = 'PoE';
break;
case 'DRAW':
title = '图表';
break;
};
return <ResultTitleBar title={curtitle} onBack={handleBack} />;
};
const renderContent = () => {
switch (view) {
case 'LENGTH':
return (
<div className="w-full h-[490px] bg-[#6b6d6b]">
<div className="w-full h-full bg-white p-4 rounded-md">
<div className="w-full">
<div className="grid grid-cols-4 text-center border-b border-gray-300 pb-2">
<div className="flex flex-col items-center">
<span className="text-blue-600 text-lg"></span>
</div>
<div className="flex flex-col items-center">
<span className="text-blue-600 text-lg">传输延迟</span>
<span className="text-gray-500">(ns)</span>
</div>
<div className="flex flex-col items-center">
<span className="text-blue-600 text-lg">延迟时差</span>
<span className="text-gray-500">(ns)</span>
</div>
<div className="flex flex-col items-center">
<span className="text-blue-600 text-lg">长度</span>
<span className="text-gray-500">(m)</span>
</div>
</div>
<div className="mt-4 space-y-4">
{[
{ pair: '1,2', color: 'text-orange-500', delay: testResult?.resultdata?.performance?.DELAY?.PAIR12, delaySkew: testResult?.resultdata?.performance?.DELAYSKEW?.PAIR12, length: testResult?.resultdata?.performance?.LENGTH?.PAIR12 },
{ pair: '3,6', color: 'text-green-500', delay: testResult?.resultdata?.performance?.DELAY?.PAIR36, delaySkew: testResult?.resultdata?.performance?.DELAYSKEW?.PAIR36, length: testResult?.resultdata?.performance?.LENGTH?.PAIR36 },
...(wireOrder !== 'Ethernet Two-Pair' && wireOrder !== 'M12-D Two-Pair' ? [
{ pair: '4,5', color: 'text-blue-500', delay: testResult?.resultdata?.performance?.DELAY?.PAIR45, delaySkew: testResult?.resultdata?.performance?.DELAYSKEW?.PAIR45, length: testResult?.resultdata?.performance?.LENGTH?.PAIR45 },
{ pair: '7,8', color: 'text-yellow-500', delay: testResult?.resultdata?.performance?.DELAY?.PAIR78, delaySkew: testResult?.resultdata?.performance?.DELAYSKEW?.PAIR78, length: testResult?.resultdata?.performance?.LENGTH?.PAIR78 }
] : [])
].map(({ pair, color, delay, delaySkew, length }) => (
<div key={pair} className={`grid grid-cols-4 text-center border-gray-300 pt-2 ${color}`}>
<div className="font-bold">{pair}</div>
<div className={limitdata && delay > limitdata.DELAY ? 'bg-red-100 rounded-md' : ''}>{delay}</div>
<div className={limitdata && delaySkew > limitdata.DELAYSKEW ? 'bg-red-100 rounded-md' : ''}>{delaySkew}</div>
<div className={`text-center ${(limitdata && length > limitdata.LENGTH) && !(limitValue?.includes('ISO') || limitValue?.includes('Profinet')) ? 'bg-red-200 rounded-md' : ''}`}>{length}</div>
</div>
))}
{limitdata && (
<div className="grid grid-cols-4 text-center border-t border-gray-300 pt-2">
<div className="font-bold">极限</div>
<div>{limitdata.DELAY}</div>
<div>{limitdata.DELAYSKEW}</div>
<div>{limitdata.LENGTH}</div>
</div>
)}
</div>
</div>
</div>
</div>
);
case 'OHM':
return (
<div className="w-full h-[490px] bg-[#6b6d6b]">
<div className="w-full h-full bg-white p-0 rounded-md">
<div className="w-full">
{/* 选项卡 */}
<div className="flex mb-4">
{['回路'].map((tab) => (
<button
key={tab}
onClick={() => setPoeTab(tab)}
className={`flex-1 py-2 text-center text-xl font-bold ${poeTab === tab ? 'bg-gradient-to-b from-[#b0b0b0] via-[#e0e4e0] to-[#fff] text-black' : 'bg-[#303030] text-[#fffe92]'}`}
>
{tab}
</button>
))}
</div>
{/* 数据展示区域 */}
{poeTab === '回路' && (
<div className="mt-4 space-y-4">
<div className="grid grid-cols-2 text-center border-b border-gray-300 pb-2">
<div className="font-bold"> </div>
<div >
<span className="text-gray-500 text-lg">(Ω)</span>
</div>
</div>
{[
{ pair: '1,2', color: 'text-orange-500', value: testResult?.resultdata?.performance?.OHM?.LOOP?.PAIR12 },
{ pair: '3,6', color: 'text-green-500', value: testResult?.resultdata?.performance?.OHM?.LOOP?.PAIR36 },
...(wireOrder !== 'Ethernet Two-Pair' && wireOrder !== 'M12-D Two-Pair' ? [
{ pair: '4,5', color: 'text-blue-500', value: testResult?.resultdata?.performance?.OHM?.LOOP?.PAIR45 },
{ pair: '7,8', color: 'text-yellow-500', value: testResult?.resultdata?.performance?.OHM?.LOOP?.PAIR78 }
] : [])
].map(({ pair, color, value }) => (
<div key={pair} className="grid grid-cols-2 text-center py-2">
<div className={`font-bold ${color}`}>{pair}</div>
<div>{value || 'N/A'}</div>
</div>
))}
<div className="grid grid-cols-2 text-center border-t border-gray-300 pt-2">
<div className="font-bold">极限</div>
{limitdata && (
<div>{limitdata.LOOP}</div>
)}
</div>
</div>
)}
</div>
</div>
</div>
);
case 'PoE':
return (
<div className="w-full h-[490px] bg-[#6b6d6b] ">
<div className="w-full h-full bg-white p-0 rounded-md">
<div className="w-full">
{/* 选项卡 */}
<div className="flex mb-4">
{['回路', '线对UBL', 'P2P UBL'].map((tab) => (
<button
key={tab}
onClick={() => setPoeTab(tab)}
className={`flex-1 py-1 text-center text-lg font-bold ${poeTab === tab ? 'bg-gradient-to-b from-[#b0b0b0] via-[#e0e4e0] to-[#fff] text-black' : 'bg-[#303030] text-[#fffe92]'}`}
>
{tab}
</button>
))}
</div>
{/* 数据展示区域 */}
{poeTab === '回路' && (
<div className="mt-4 space-y-4">
<div className="grid grid-cols-2 text-center border-b border-gray-300 pb-2">
<div className="font-bold"> </div>
<div >
<span className="text-gray-500 text-lg">(Ω)</span>
</div>
</div>
{[
{ pair: '1,2', color: 'text-orange-500', value: testResult?.resultdata?.performance?.OHM?.LOOP?.PAIR12 },
{ pair: '3,6', color: 'text-green-500', value: testResult?.resultdata?.performance?.OHM?.LOOP?.PAIR36 },
...(wireOrder !== 'Ethernet Two-Pair' && wireOrder !== 'M12-D Two-Pair' ? [
{ pair: '4,5', color: 'text-blue-500', value: testResult?.resultdata?.performance?.OHM?.LOOP?.PAIR45 },
{ pair: '7,8', color: 'text-yellow-500', value: testResult?.resultdata?.performance?.OHM?.LOOP?.PAIR78 }
] : [])
].map(({ pair, color, value }) => (
<div key={pair} className="grid grid-cols-2 text-center py-2">
<div className={`font-bold ${color}`}>{pair}</div>
<div className={`${value > limitdata?.LOOP ? 'bg-red-100' : ''}`}>
{value || 'N/A'}
</div>
</div>
))}
<div className="grid grid-cols-2 text-center border-t border-gray-300 pt-2">
<div className="font-bold">极限</div>
{limitdata && (
<div>{limitdata.LOOP}</div>
)}
</div>
</div>
)}
{poeTab === '线对UBL' && (
<div className="mt-4 space-y-4">
<div className="grid grid-cols-3 text-center border-b border-gray-300 pb-2">
<div className="font-bold"> </div>
<div className="flex flex-col items-center">
<span className="text-gray-500 text-lg">(Ω)</span>
</div>
<div className="flex flex-col items-center">
<span className="text-gray-500 text-lg">极限(Ω)</span>
</div>
</div>
{[
{ pair: '1,2', color: 'text-orange-500', value: testResult?.resultdata?.performance?.OHM?.PAIRUBL?.PAIR12 },
{ pair: '3,6', color: 'text-green-500', value: testResult?.resultdata?.performance?.OHM?.PAIRUBL?.PAIR36 },
...(wireOrder !== 'Ethernet Two-Pair' && wireOrder !== 'M12-D Two-Pair' ? [
{ pair: '4,5', color: 'text-blue-500', value: testResult?.resultdata?.performance?.OHM?.PAIRUBL?.PAIR45 },
{ pair: '7,8', color: 'text-yellow-500', value: testResult?.resultdata?.performance?.OHM?.PAIRUBL?.PAIR78 }
] : [])
].map(({ pair, color, value }) => (
<div key={pair} className="grid grid-cols-3 text-center py-2">
<div className={`font-bold ${color}`}>{pair}</div>
<div className={`${value > limitdata?.PAIRUBL ? 'bg-red-100' : ''}`}>
{value || 'N/A'}
</div>
{limitdata && (
<div>{limitdata.PAIRUBL}</div>
)}
</div>
))}
</div>
)}
{poeTab === 'P2P UBL' && (
<div className="mt-4 space-y-4">
<div className="grid grid-cols-3 text-center border-b border-gray-300 pb-2">
<div className="font-bold"> </div>
<div className="flex flex-col items-center">
<span className="text-gray-500 text-lg">(Ω)</span>
</div>
<div className="flex flex-col items-center">
<span className="text-gray-500 text-lg">极限(Ω)</span>
</div>
</div>
{[
{ pair: '12-36', color: 'text-orange-500', value: testResult?.resultdata?.performance?.OHM?.P2PUBL?.PAIR1236 },
...(wireOrder !== 'Ethernet Two-Pair' && wireOrder !== 'M12-D Two-Pair' ? [
{ pair: '12-45', color: 'text-green-500', value: testResult?.resultdata?.performance?.OHM?.P2PUBL?.PAIR1245 },
{ pair: '12-78', color: 'text-blue-500', value: testResult?.resultdata?.performance?.OHM?.P2PUBL?.PAIR1278 },
{ pair: '36-45', color: 'text-yellow-500', value: testResult?.resultdata?.performance?.OHM?.P2PUBL?.PAIR3645 },
{ pair: '36-78', color: 'text-purple-500', value: testResult?.resultdata?.performance?.OHM?.P2PUBL?.PAIR3678 },
{ pair: '45-78', color: 'text-pink-500', value: testResult?.resultdata?.performance?.OHM?.P2PUBL?.PAIR4578 }
] : [])
].map(({ pair, color, value }) => (
<div key={pair} className="grid grid-cols-3 text-center py-2">
<div className={`font-bold ${color}`}>{pair}</div>
<div className={`${value > limitdata?.P2PUBL ? 'bg-red-100' : ''}`}>
{value || 'N/A'}
</div>
{limitdata && (
<div>{limitdata.P2PUBL}</div>
)}
</div>
))}
</div>
)}
</div>
</div>
</div>
);
case 'DRAW':
return (
<div className="w-full h-[full] bg-[#6b6d6b]">
<div className="w-full h-full bg-white p-4 rounded-md">
{limitdata && (
<FrequencyChart
curtitle={curtitle}
limitValue={limitValue}
limitdata={limitdata}
wireOrder={wireOrder}
data={testResult?.resultdata?.performance?.data}
/>
)}
</div>
</div>
);
case 'HDTD':
return (
<div className="w-full h-[full] bg-[#6b6d6b]">
<div className="w-full h-full bg-white p-4 rounded-md">
{limitdata && (
<HDTDChart
curtitle={curtitle}
HDTD={testResult?.resultdata?.HDTD}
/>
)}
</div>
</div>
);
default:
return null;
}
};
const renderFooter = () => {
return (
<div className="h-[60px] bg-[#303030] flex items-center justify-end px-8">
</div>
);
}
return (
<div className="w-full h-full flex flex-col overflow-hidden">
<StatusBar />
{renderResultTitleBar()}
{renderContent()}
{renderFooter()}
</div>
);
}

View File

@@ -0,0 +1,474 @@
import React, { useEffect } from 'react';
import Image from 'next/image';
import StatusBar from '../lib/StatusBar';
import TitleBar from '../lib/TitleBar';
import useDisplayStore from '@/store/displayStore';
import useDeviceStore from '@/store/deviceStore';
export default function HomePage() {
const {
getCurrentProject,
getCurrentTestConfig,
getCurrentOperator,
getCurrentCableId,
getCurrentCableId2,
navigateTo,
hasPlayedSound,
setToastMessage
} = useDisplayStore();
const { ref } = useDisplayStore.getState();
useEffect(() => {
if (hasPlayedSound) {
useDisplayStore.setState({ hasPlayedSound: false });
}
}, [hasPlayedSound]);
const { connectionStatus, mainUnitAdapter, remoteUnitAdapter,mainUnitModules,setTotalToastMessage } = useDeviceStore();
// 获取当前项目数据
const currentProject = getCurrentProject();
// 获取当前选中的测试配置
const currentConfig = getCurrentTestConfig();
// 获取当前选中的操作员
const currentOperator = getCurrentOperator();
// 获取当前选中的电缆ID
const currentCableId = getCurrentCableId();
// 获取当前选中的电缆ID2
const currentCableId2 = getCurrentCableId2();
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;
const ConnectStatus = connectionStatus?.CFPConnectStatus ?? connectionStatus?.['8000ConnectStatus'];
// 计算下一个线缆ID
const getNextCableId = (currentId) => {
const match = currentId.match(/^(.*?)(\d+)$/);
if (match) {
const prefix = match[1];
const number = parseInt(match[2]) + 1;
return `${prefix}${number.toString().padStart(match[2].length, '0')}`;
}
return currentId;
};
const handleProjectClick = () => {
navigateTo('project', 'main');
};
const handleOperatorsClick = () => {
navigateTo('operators', 'main');
};
// 渲染测试配置区域的内容
const renderTestConfigContent = () => {
if (!currentConfig) return null;
switch (currentConfig.moduleType) {
case '8000':
return (
<div className="flex flex-col justify-center h-full space-y-0.2 text-sm">
<div className="text-black text-sm">{currentConfig.params.limitValue}</div>
<div className="text-black text-sm">{currentConfig.params.cableType}</div>
<div className="text-black text-sm">{currentConfig.params.wireOrder}</div>
</div>
);
case 'cfp':
return (
<div className="flex flex-col justify-center h-full space-y-0.2 text-sm">
<div className="text-black text-sm">智能远端</div>
<div className="text-black text-sm">{currentConfig.params.limitValue}</div>
<div className="text-black text-sm">{currentConfig.params.cableType}</div>
<div className="text-black text-sm">{currentConfig.params.refJumper} 跳线参照</div>
</div>
);
case 'ofp':
return (
<div className="flex flex-col justify-center h-full space-y-0.2 text-sm">
<div className="text-black text-sm">自动OTDR</div>
<div className="text-black text-sm">{currentConfig.params.limitValue}</div>
<div className="text-black text-sm">{currentConfig.params.cableType}</div>
</div>
);
default:
return null;
}
};
return (
<div className="w-full h-full flex flex-col overflow-hidden">
<StatusBar />
<TitleBar title="HOME" />
<div className="h-[490px] bg-[#303040] p-2 flex flex-col">
{/* 上方信息区域 */}
<div className="h-[315px] bg-[#c5c2c5] rounded-sm p-3 shadow-lg flex flex-col gap-2">
{/* 项目配置部分 */}
<div className="flex justify-between items-start cursor-pointer" onClick={handleProjectClick}>
<div>
<div className="text-lg font-bold mb-2">项目{currentProject?.name}</div>
<div className="flex items-center gap-4">
<div className="flex items-center gap-1">
<span className="text-green-500">{passCount|| 0}</span>
<div className="w-3 h-3 relative">
<Image
src="/pass.png"
alt="通过"
sizes={"auto"}
fill
className="object-contain"
/>
</div>
</div>
<div className="flex items-center gap-1">
<span className="text-red-500">{failCount|| 0}</span>
<div className="w-3 h-3 relative">
<Image
src="/fail.png"
alt="失败"
sizes={"auto"}
fill
className="object-contain"
/>
</div>
</div>
</div>
</div>
<div className="w-6 h-6 relative">
<Image
src="/arrow.png"
alt="箭头"
sizes={"auto"}
fill
className="object-contain"
/>
</div>
</div>
{/* 测试配置区域 */}
<div
className="h-[100px] bg-gradient-to-b from-[#e6e3e6] to-[#7b797b] rounded-sm p-4 shadow-lg flex justify-between items-center cursor-pointer"
onClick={() => navigateTo('testConfig', 'main')}
>
{renderTestConfigContent()}
<div className="flex items-center gap-2">
<span className="text-gray-500 text-sm">{currentConfig?.modulelable}</span>
<div className="w-3 h-3 relative">
<Image
src="/arrow.png"
alt="箭头"
sizes={"auto"}
fill
className="object-contain"
/>
</div>
</div>
</div>
{/* 线缆ID区域 */}
<div
className="h-[60px] bg-gradient-to-b from-[#e6e3e6] to-[#7b797b] rounded-sm p-4 shadow-lg flex justify-between items-center cursor-pointer"
onClick={() => navigateTo('cableId', currentConfig?.moduleType === 'cfp' ? 'cfp' : 'main')}
>
<div className="flex flex-col justify-center h-full space-y-0.2 text-sm">
{currentConfig?.moduleType === 'cfp' ? (
<>
<div className="text-black">下一个输入ID: {currentCableId?.name}</div>
<div className="text-black">下一个输出ID: {currentCableId2?.name}</div>
</>
) : (
<div className="text-black">下一个ID:
<p>{currentCableId?.name}</p>
</div>
)}
</div>
<div className="w-3 h-3 relative">
<Image
src="/arrow.png"
alt="箭头"
sizes={"auto"}
fill
className="object-contain"
/>
</div>
</div>
{/* 操作员区域 */}
<div
className="h-[40px] bg-gradient-to-b from-[#e6e3e6] to-[#7b797b] rounded-sm p-4 shadow-lg flex justify-between items-center cursor-pointer"
onClick={handleOperatorsClick}
>
<div className="text-black text-sm">操作员: {currentOperator?.name}</div>
<div className="w-3 h-3 relative">
<Image
src="/arrow.png"
alt="箭头"
sizes={"auto"}
fill
className="object-contain"
/>
</div>
</div>
</div>
{/* 底部按钮区域 */}
<div className="flex-1 flex items-center justify-center gap-16">
<div className="flex flex-col items-center gap-2"
onClick={() => navigateTo('tools', 'main')}>
<div className="w-20 h-20 rounded-sm shadow-lg flex items-center justify-center cursor-pointer">
<div className="w-full h-full relative">
<Image
src="/tools.png"
alt="工具"
sizes={"auto"}
fill
className="object-contain"
/>
</div>
</div>
<span className="text-white text-sm">工具</span>
</div>
<div
className="flex flex-col items-center gap-2 cursor-pointer"
onClick={() => navigateTo('result', 'main')}
>
<div className="w-20 h-20 rounded-sm shadow-lg flex items-center justify-center">
<div className="w-full h-full relative">
<Image
src="/result.png"
alt="结果"
sizes={"auto"}
fill
className="object-contain"
/>
</div>
</div>
<span className="text-white text-sm">结果</span>
</div>
</div>
</div>
{/* 底部栏 */}
<div className="h-[60px] bg-[#303030] flex items-center justify-between px-5">
{/* 设置参照按钮 */}
<div className="flex-1"></div>
<div className="flex-1 flex justify-center">
{(currentConfig?.moduleType === 'cfp' || currentConfig?.moduleType === 'ofp') && (
<button
className="w-[120px] h-[40px] bg-gradient-to-b from-[#656565] to-[#313431] rounded-sm flex items-center justify-center text-white font-bold shadow-lg"
onClick={() => {
// 根据模块类型设置不同的参照逻辑
if (currentConfig?.moduleType === 'cfp') {
navigateTo('tools', 'ref-cfp');
} else if (currentConfig?.moduleType === 'ofp') {
navigateTo('tools', 'ref-ofp');
}
}}
>
{currentConfig?.moduleType === 'ofp' ? '设置基准' : '设置参照'}
</button>
)}
</div>
{/* 测试按钮 */}
<div className="flex-1 flex justify-end">
<button
className="w-[100px] h-[40px] bg-gradient-to-b from-[#ffd773] to-[#e7aa29] rounded-sm flex items-center justify-center text-black font-bold shadow-lg"
onClick={() => {
if (currentConfig?.moduleType === '8000' || currentConfig?.moduleType === 'cfp') {
if (!ConnectStatus) {
setToastMessage('未连接,请检查连接状态');
return;
}
}
if (currentConfig?.moduleType === '8000') {
if (mainUnitModules?.[0]?.id !== '8000') {
setToastMessage('当前模块与所选标准不兼容');
return;
}
const limitValue = currentConfig.params.limitValue;
if (limitValue.includes('Channel') || limitValue.includes('Ch')) {
if (!mainUnitAdapter.id.includes('channel') || !remoteUnitAdapter.id.includes('channel')) {
setToastMessage('当前适配器与所选标准不兼容');
return;
}
} else if (limitValue.includes('Perm') || limitValue.includes('PL')) {
if (!mainUnitAdapter.id.includes('permanent') || !remoteUnitAdapter.id.includes('permanent')) {
setToastMessage('当前适配器与所选标准不兼容');
return;
}
} else if (limitValue.includes('MPTL')) {
const hasPermanent = mainUnitAdapter.id.includes('permanent') || remoteUnitAdapter.id.includes('permanent');
const hasPatchcode = mainUnitAdapter.id.includes('patchcode') || remoteUnitAdapter.id.includes('patchcode');
if (!(hasPermanent && hasPatchcode)) {
setToastMessage('当前适配器与所选标准不兼容');
return;
}
}
}
if (currentConfig?.moduleType === 'cfp') {
if (mainUnitModules?.[0]?.id !== 'cfp') {
setToastMessage('当前模块与所选标准不兼容');
return;
}
// 检查光纤类型
const cableType = currentConfig.params.cableType;
const isMultiMode = cableType.includes('OM');
// 根据单/多模获取对应的路径
let mainOutPath, mainInPath, remoteOutPath, remoteInPath;
if (isMultiMode) {
// 多模光纤路径
mainOutPath = connectionStatus?.mainPaths?.['main-cfp-mm-out']?.path || [];
mainInPath = connectionStatus?.mainPaths?.['main-cfp-in']?.path || [];
remoteOutPath = connectionStatus?.remotePaths?.['remote-cfp-mm-out']?.path || [];
remoteInPath = connectionStatus?.remotePaths?.['remote-cfp-in']?.path || [];
// 检查是否所有端口都已连接
if (!mainOutPath.length || !mainInPath.length || !remoteOutPath.length || !remoteInPath.length) {
setTotalToastMessage('请确保所有光纤端口都已正确连接');
return;
}
// 检查是否存在单模跳线
const hasInvalidConnector = [...mainOutPath, ...mainInPath, ...remoteOutPath, ...remoteInPath].some(item => {
return item.includes('sm');
});
if (hasInvalidConnector) {
setTotalToastMessage('多模光纤链路中存在单模跳线,请检查连接');
return;
}
} else {
// 单模光纤路径
mainOutPath = connectionStatus?.mainPaths?.['main-cfp-sm-out']?.path || [];
mainInPath = connectionStatus?.mainPaths?.['main-cfp-in']?.path || [];
remoteOutPath = connectionStatus?.remotePaths?.['remote-cfp-sm-out']?.path || [];
remoteInPath = connectionStatus?.remotePaths?.['remote-cfp-in']?.path || [];
// 检查是否所有端口都已连接
if (!mainOutPath.length || !mainInPath.length || !remoteOutPath.length || !remoteInPath.length) {
setTotalToastMessage('请确保所有光纤端口都已正确连接');
return;
}
// 检查是否存在多模跳线
const hasInvalidConnector = [...mainOutPath, ...mainInPath, ...remoteOutPath, ...remoteInPath].some(item => {
return item.includes('mm');
});
if (hasInvalidConnector) {
setTotalToastMessage('单模光纤链路中存在多模跳线,请检查连接');
return;
}
}
}
if (currentConfig?.moduleType === 'ofp') {
if (mainUnitModules?.[0]?.id !== 'ofp') {
setToastMessage('当前模块与所选标准不兼容');
return;
}
// 检查光纤类型是否匹配
const cableType = currentConfig.params.cableType;
const isMultiMode = cableType.includes('OM');
const portType = Object.values(connectionStatus?.mainPaths || {})[0]?.start;
if (isMultiMode && portType !== 'main-ofp-mm-out') {
setTotalToastMessage('当前光纤类型必须使用多模端口测试');
return;
}
if (!isMultiMode && portType !== 'main-ofp-sm-out') {
setTotalToastMessage('当前光纤类型必须使用单模端口测试');
return;
}
// 检查光纤连接器类型是否匹配
const path = Object.values(connectionStatus?.mainPaths || {})[0]?.path || [];
const hasInvalidConnector = path.some(item => {
if (isMultiMode) {
return item.includes('sm-') || item.includes('smc-');
} else {
return item.includes('mm-') || item.includes('mmc-');
}
});
if (hasInvalidConnector) {
setTotalToastMessage(isMultiMode ? '未检测到多模光纤,请检查光纤连接' : '未检测到单模光纤,请检查光纤连接');
return;
}
if (!connectionStatus?.OFPStatus) {
setTotalToastMessage('请接入实际被测链路,进行测试');
return;
}
// 检查光纤连接数量
const fiberConnectorCount = path.filter(item => {
return item.includes('sm-') || item.includes('mm-') ||
item.includes('smc-') || item.includes('mmc-');
}).length;
if (fiberConnectorCount > 3) {
setTotalToastMessage('该场景仅支持使用一段光纤连接测试仪与链路');
return;
}
}
navigateTo('testing','nosave');
}}
>
测试
</button>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,681 @@
import React, { useState,useEffect } from 'react';
import StatusBar from '../lib/StatusBar';
import TitleBar from '../lib/TitleBar';
import SubTitleBar from '../lib/SubTitleBar';
import useDisplayStore from '@/store/displayStore';
// 菜单数据定义
const menuData = {
MODULE: {
title: '模块',
items: [
{ value: '8000', label: 'DSX-8000' },
{ value: 'cfp', label: 'CertiFiber Pro-Quad' },
{ value: 'ofp', label: 'OptiFiber Pro-Quad' }
]
},
CABLE_TYPE: {
title: '电缆类型',
subTitle: '电缆类型',
items: [
{ value: 'custom', label: '定制', disabled: true },
{
value: 'general',
label: '通用',
children: [
// { value: 'Cat8 S/FTP', label: 'Cat8 S/FTP' },
// { value: 'Cat7A S/FTP', label: 'Cat7A S/FTP' },
// { value: 'Cat7 S/FTP', label: 'Cat7 S/FTP' },
// { value: 'Cat6A S/FTP', label: 'Cat6A S/FTP' },
{ value: 'Cat6A U/UTP', label: 'Cat6A U/UTP' },
{ value: 'Cat6A F/UTP', label: 'Cat6A F/UTP' },
{ value: 'Cat6 U/UTP', label: 'Cat6 U/UTP' },
{ value: 'Cat6 F/UTP', label: 'Cat6 F/UTP' },
// { value: 'Cat6 U/FTP', label: 'Cat6 U/FTP' },
{ value: 'Cat5e U/UTP', label: 'Cat5e U/UTP' },
{ value: 'Cat5e F/UTP', label: 'Cat5e F/UTP' },
// { value: 'Cat5 U/UTP', label: 'Cat5 U/UTP' },
// { value: 'Cat5 F/UTP', label: 'Cat5 F/UTP' }
]
},
{ value: 'coaxial', label: '通用同轴电缆', disabled: true },
{ value: 'manufacturer', label: '制造商', disabled: true }
]
},
LIMIT_VALUE: {
title: '测试极限值',
subTitle: '极限值组',
items: [
{
value: 'TIA',
label: 'TIA',
children: {
// Cat8: {
// label: 'Cat8',
// children: [
// { value: 'TIA Cat 8 Perm.Link', label: 'TIA Cat 8 Perm.Link' },
// { value: 'TIA Cat 8 Perm.Link (+ALL)', label: 'TIA Cat 8 Perm.Link (+ALL)' },
// { value: 'TIA Cat 8 Perm.Link (+PoE)', label: 'TIA Cat 8 Perm.Link (+PoE)' },
// { value: 'TIA Cat 8 Channel', label: 'TIA Cat 8 Channel' },
// { value: 'TIA Cat 8 Channel (+ALL)', label: 'TIA Cat 8 Channel (+ALL)' },
// { value: 'TIA Cat 8 Channel (+PoE)', label: 'TIA Cat 8 Channel (+PoE)' }
// ]
// },
Cat6A: {
label: 'Cat6A',
children: [
{ value: 'TIA Cat 6A Perm.Link', label: 'TIA Cat 6A Perm.Link' },
{ value: 'TIA Cat 6A Perm.Link (+ALL)', label: 'TIA Cat 6A Perm.Link (+ALL)' },
{ value: 'TIA Cat 6A Perm.Link (+PoE)', label: 'TIA Cat 6A Perm.Link (+PoE)' },
{ value: 'TIA Cat 6A Channel', label: 'TIA Cat 6A Channel' },
{ value: 'TIA Cat 6A Channel (+ALL)', label: 'TIA Cat 6A Channel (+ALL)' },
{ value: 'TIA Cat 6A Channel (+PoE)', label: 'TIA Cat 6A Channel (+PoE)' },
{ value: 'TIA Cat 6A MPTL', label: 'TIA Cat 6A MPTL' },
{ value: 'TIA Cat 6A MPTL (+PoE)', label: 'TIA Cat 6A MPTL (+PoE)' },
// { value: 'TIA 1005 Cat 6A Perm.Link', label: 'TIA 1005 Cat 6A Perm.Link' },
// { value: 'TIA 1005 Cat 6A Channel', label: 'TIA 1005 Cat 6A Channel' },
{ value: 'TIA 1005 Cat 6A Channel E1 (+ALL)', label: 'TIA 1005 Cat 6A Channel E1 (+ALL)' },
// { value: 'TIA 1005 Cat 6A Channel E1 (+PoE)', label: 'TIA 1005 Cat 6A Channel E1 (+PoE)' },
{ value: 'TIA 1005 Cat 6A Channel E2 (+ALL)', label: 'TIA 1005 Cat 6A Channel E2 (+ALL)' },
// { value: 'TIA 1005 Cat 6A Channel E2 (+PoE)', label: 'TIA 1005 Cat 6A Channel E2 (+PoE)' },
{ value: 'TIA 1005 Cat 6A Channel E3 (+ALL)', label: 'TIA 1005 Cat 6A Channel E3 (+ALL)' },
// { value: 'TIA 1005 Cat 6A Channel E3 (+PoE)', label: 'TIA 1005 Cat 6A Channel E3 (+PoE)' }
]
},
Cat6: {
label: 'Cat6',
children: [
{ value: 'TIA Cat 6 Perm.Link', label: 'TIA Cat 6 Perm.Link' },
{ value: 'TIA Cat 6 Perm.Link (+ALL)', label: 'TIA Cat 6 Perm.Link (+ALL)' },
{ value: 'TIA Cat 6 Perm.Link (+PoE)', label: 'TIA Cat 6 Perm.Link (+PoE)' },
{ value: 'TIA Cat 6 Channel', label: 'TIA Cat 6 Channel' },
{ value: 'TIA Cat 6 Channel (+ALL)', label: 'TIA Cat 6 Channel (+ALL)' },
{ value: 'TIA Cat 6 Channel (+PoE)', label: 'TIA Cat 6 Channel (+PoE)' },
{ value: 'TIA Cat 6 MPTL', label: 'TIA Cat 6 MPTL' },
{ value: 'TIA Cat 6 MPTL (+PoE)', label: 'TIA Cat 6 MPTL (+PoE)' },
// { value: 'TIA 1005 Cat 6 Perm.Link', label: 'TIA 1005 Cat 6 Perm.Link' },
// { value: 'TIA 1005 Cat 6 Channel', label: 'TIA 1005 Cat 6 Channel' },
// { value: 'TIA 1005 Cat 6 Channel (+ALL)', label: 'TIA 1005 Cat 6 Channel (+ALL)' },
// { value: 'TIA 1005 Cat 6 Channel (+PoE)', label: 'TIA 1005 Cat 6 Channel (+PoE)' },
{ value: 'TIA 1005 Cat 6 Channel E1 (+ALL)', label: 'TIA 1005 Cat 6 Channel E1 (+ALL)' },
// { value: 'TIA 1005 Cat 6 Channel E1 (+PoE)', label: 'TIA 1005 Cat 6 Channel E1 (+PoE)' },
{ value: 'TIA 1005 Cat 6 Channel E2 (+ALL)', label: 'TIA 1005 Cat 6 Channel E2 (+ALL)' },
// { value: 'TIA 1005 Cat 6 Channel E2 (+PoE)', label: 'TIA 1005 Cat 6 Channel E2 (+PoE)' },
{ value: 'TIA 1005 Cat 6 Channel E3 (+ALL)', label: 'TIA 1005 Cat 6 Channel E3 (+ALL)' },
// { value: 'TIA 1005 Cat 6 Channel E3 (+PoE)', label: 'TIA 1005 Cat 6 Channel E3 (+PoE)' }
]
},
Cat5e: {
label: 'Cat5e',
children: [
{ value: 'TIA Cat 5e Perm.Link', label: 'TIA Cat 5e Perm.Link' },
{ value: 'TIA Cat 5e Perm.Link (+ALL)', label: 'TIA Cat 5e Perm.Link (+ALL)' },
{ value: 'TIA Cat 5e Perm.Link (+PoE)', label: 'TIA Cat 5e Perm.Link (+PoE)' },
{ value: 'TIA Cat 5e Channel', label: 'TIA Cat 5e Channel' },
{ value: 'TIA Cat 5e Channel (+ALL)', label: 'TIA Cat 5e Channel (+ALL)' },
{ value: 'TIA Cat 5e Channel (+PoE)', label: 'TIA Cat 5e Channel (+PoE)' },
{ value: 'TIA Cat 5e MPTL', label: 'TIA Cat 5e MPTL' },
{ value: 'TIA Cat 5e MPTL (+PoE)', label: 'TIA Cat 5e MPTL (+PoE)' },
// { value: 'TIA 1005 Cat 5e Perm.Link', label: 'TIA 1005 Cat 5e Perm.Link' },
// { value: 'TIA 1005 Cat 5e Channel', label: 'TIA 1005 Cat 5e Channel' },
// { value: 'TIA 1005 Cat 5e Channel (+ALL)', label: 'TIA 1005 Cat 5e Channel (+ALL)' },
// { value: 'TIA 1005 Cat 5e Channel (+PoE)', label: 'TIA 1005 Cat 5e Channel (+PoE)' },
{ value: 'TIA 1005 Cat 5e Channel E1 (+ALL)', label: 'TIA 1005 Cat 5e Channel E1 (+ALL)' },
// { value: 'TIA 1005 Cat 5e Channel E1 (+PoE)', label: 'TIA 1005 Cat 5e Channel E1 (+PoE)' },
{ value: 'TIA 1005 Cat 5e Channel E2 (+ALL)', label: 'TIA 1005 Cat 5e Channel E2 (+ALL)' },
// { value: 'TIA 1005 Cat 5e Channel E2 (+PoE)', label: 'TIA 1005 Cat 5e Channel E2 (+PoE)' },
{ value: 'TIA 1005 Cat 5e Channel E3 (+ALL)', label: 'TIA 1005 Cat 5e Channel E3 (+ALL)' },
// { value: 'TIA 1005 Cat 5e Channel E3 (+PoE)', label: 'TIA 1005 Cat 5e Channel E3 (+PoE)' }
]
}
}
},
{
value: 'ISO',
label: 'ISO',
children: {
// 'Class Ⅱ': {
// label: 'Class Ⅱ',
// children: [
// { value: 'ISO11801 PL Class Ⅱ', label: 'ISO11801 PL Class Ⅱ' },
// { value: 'ISO11801 PL Class Ⅱ (+ALL)', label: 'ISO11801 PL Class Ⅱ (+ALL)' },
// { value: 'ISO11801 PL Class Ⅱ (+PoE)', label: 'ISO11801 PL Class Ⅱ (+PoE)' },
// { value: 'ISO11801 Channel Class Ⅱ', label: 'ISO11801 Channel Class Ⅱ' },
// { value: 'ISO11801 Channel Class Ⅱ (+ALL)', label: 'ISO11801 Channel Class Ⅱ (+ALL)' },
// { value: 'ISO11801 Channel Class Ⅱ (+PoE)', label: 'ISO11801 Channel Class Ⅱ (+PoE)' }
// ],
// disabled: true
// },
// 'Class ': {
// label: 'Class ',
// children: [
// { value: 'ISO11801 PL Class ', label: 'ISO11801 PL Class ' },
// { value: 'ISO11801 PL Class (+ALL)', label: 'ISO11801 PL Class (+ALL)' },
// { value: 'ISO11801 PL Class (+PoE)', label: 'ISO11801 PL Class (+PoE)' },
// { value: 'ISO11801 Channel Class ', label: 'ISO11801 Channel Class ' },
// { value: 'ISO11801 Channel Class (+ALL)', label: 'ISO11801 Channel Class (+ALL)' },
// { value: 'ISO11801 Channel Class (+PoE)', label: 'ISO11801 Channel Class (+PoE)' }
// ],
// disabled: true
// },
// 'Class Fa': {
// label: 'Class Fa',
// children: [
// { value: 'ISO11801 PL2 Class Fa', label: 'ISO11801 PL2 Class Fa' },
// { value: 'ISO11801 PL2 Class Fa (+ALL)', label: 'ISO11801 PL2 Class Fa (+ALL)' },
// { value: 'ISO11801 PL2 Class Fa (+PoE)', label: 'ISO11801 PL2 Class Fa (+PoE)' },
// { value: 'ISO11801 PL3 Class Fa', label: 'ISO11801 PL3 Class Fa' },
// { value: 'ISO11801 PL3 Class Fa (+ALL)', label: 'ISO11801 PL3 Class Fa (+ALL)' },
// { value: 'ISO11801 PL3 Class Fa (+PoE)', label: 'ISO11801 PL3 Class Fa (+PoE)' },
// { value: 'ISO11801 Channel Class Fa', label: 'ISO11801 Channel Class Fa' },
// { value: 'ISO11801 Channel Class Fa (+ALL)', label: 'ISO11801 Channel Class Fa (+ALL)' },
// { value: 'ISO11801 Channel Class Fa (+PoE)', label: 'ISO11801 Channel Class Fa (+PoE)' }
// ],
// disabled: true
// },
// 'Class F': {
// label: 'Class F',
// children: [
// { value: 'ISO11801 PL Class F', label: 'ISO11801 PL Class F' },
// { value: 'ISO11801 PL Class F (+ALL)', label: 'ISO11801 PL Class F (+ALL)' },
// { value: 'ISO11801 PL Class F (+PoE)', label: 'ISO11801 PL Class F (+PoE)' },
// { value: 'ISO11801 Channel Class F', label: 'ISO11801 Channel Class F' },
// { value: 'ISO11801 Channel Class F (+ALL)', label: 'ISO11801 Channel Class F (+ALL)' },
// { value: 'ISO11801 Channel Class F (+PoE)', label: 'ISO11801 Channel Class F (+PoE)' }
// ],
// disabled: true
// },
'Class Ea': {
label: 'Class Ea',
children: [
{ value: 'ISO11801 PL2 Class Ea', label: 'ISO11801 PL2 Class Ea' },
{ value: 'ISO11801 PL2 Class Ea (+ALL)', label: 'ISO11801 PL2 Class Ea (+ALL)' },
{ value: 'ISO11801 PL2 Class Ea (+PoE)', label: 'ISO11801 PL2 Class Ea (+PoE)' },
{ value: 'ISO11801 PL3 Class Ea', label: 'ISO11801 PL3 Class Ea' },
{ value: 'ISO11801 PL3 Class Ea (+ALL)', label: 'ISO11801 PL3 Class Ea (+ALL)' },
{ value: 'ISO11801 PL3 Class Ea (+PoE)', label: 'ISO11801 PL3 Class Ea (+PoE)' },
{ value: 'ISO11801 Channel Class Ea', label: 'ISO11801 Channel Class Ea' },
{ value: 'ISO11801 Channel Class Ea (+ALL)', label: 'ISO11801 Channel Class Ea (+ALL)' },
{ value: 'ISO11801 Channel Class Ea (+PoE)', label: 'ISO11801 Channel Class Ea (+PoE)' },
{ value: 'ISO MPTL Class Ea', label: 'ISO MPTL Class Ea' },
{ value: 'ISO MPTL Class Ea (+PoE)', label: 'ISO MPTL Class Ea (+PoE)' }
]
},
'Class E': {
label: 'Class E',
children: [
{ value: 'ISO11801 PL Class E', label: 'ISO11801 PL Class E' },
{ value: 'ISO11801 PL Class E (+ALL)', label: 'ISO11801 PL Class E (+ALL)' },
{ value: 'ISO11801 PL Class E (+PoE)', label: 'ISO11801 PL Class E (+PoE)' },
{ value: 'ISO11801 Channel Class E', label: 'ISO11801 Channel Class E' },
{ value: 'ISO11801 Channel Class E (+ALL)', label: 'ISO11801 Channel Class E (+ALL)' },
{ value: 'ISO11801 Channel Class E (+PoE)', label: 'ISO11801 Channel Class E (+PoE)' },
{ value: 'ISO MPTL Class E', label: 'ISO MPTL Class E' },
{ value: 'ISO MPTL Class E (+PoE)', label: 'ISO MPTL Class E (+PoE)' }
]
},
'Class D': {
label: 'Class D',
children: [
{ value: 'ISO11801 PL Class D', label: 'ISO11801 PL Class D' },
{ value: 'ISO11801 PL Class D (+ALL)', label: 'ISO11801 PL Class D (+ALL)' },
{ value: 'ISO11801 PL Class D (+PoE)', label: 'ISO11801 PL Class D (+PoE)' },
{ value: 'ISO11801 Channel Class D', label: 'ISO11801 Channel Class D' },
{ value: 'ISO11801 Channel Class D (+ALL)', label: 'ISO11801 Channel Class D (+ALL)' },
{ value: 'ISO11801 Channel Class D (+PoE)', label: 'ISO11801 Channel Class D (+PoE)' },
{ value: 'ISO MPTL Class D', label: 'ISO MPTL Class D' },
{ value: 'ISO MPTL Class D (+PoE)', label: 'ISO MPTL Class D (+PoE)' }
]
}
}
},
{
value: '区域',
label: '区域',
children: {
'中国': {
label: '中国',
children: [
// { value: 'GBT 50312-2016 Cat 7A PL no CP', label: 'GB/T 50312-2016 Cat 7A PL no CP' },
// { value: 'GBT 50312-2016 Cat 7A PL no CP (+ALL)', label: 'GB/T 50312-2016 Cat 7A PL no CP (+ALL)' },
// { value: 'GBT 50312-2016 Cat 7A PL no CP (+PoE)', label: 'GB/T 50312-2016 Cat 7A PL no CP (+PoE)' },
// { value: 'GBT 50312-2016 Cat 7A PL with CP', label: 'GB/T 50312-2016 Cat 7A PL with CP' },
// { value: 'GBT 50312-2016 Cat 7A PL with CP (+ALL)', label: 'GB/T 50312-2016 Cat 7A PL with CP (+ALL)' },
// { value: 'GBT 50312-2016 Cat 7A PL with CP (+PoE)', label: 'GB/T 50312-2016 Cat 7A PL with CP (+PoE)' },
// { value: 'GBT 50312-2016 Cat 7A Ch', label: 'GB/T 50312-2016 Cat 7A Ch' },
// { value: 'GBT 50312-2016 Cat 7A Ch (+ALL)', label: 'GB/T 50312-2016 Cat 7A Ch (+ALL)' },
// { value: 'GBT 50312-2016 Cat 7A Ch (+PoE)', label: 'GB/T 50312-2016 Cat 7A Ch (+PoE)' },
// { value: 'GBT 50312-2016 Cat 7 PL', label: 'GB/T 50312-2016 Cat 7 PL' },
// { value: 'GBT 50312-2016 Cat 7 PL (+ALL)', label: 'GB/T 50312-2016 Cat 7 PL (+ALL)' },
// { value: 'GBT 50312-2016 Cat 7 PL (+PoE)', label: 'GB/T 50312-2016 Cat 7 PL (+PoE)' },
// { value: 'GBT 50312-2016 Cat 7 Ch', label: 'GB/T 50312-2016 Cat 7 Ch' },
// { value: 'GBT 50312-2016 Cat 7 Ch (+ALL)', label: 'GB/T 50312-2016 Cat 7 Ch (+ALL)' },
// { value: 'GBT 50312-2016 Cat 7 Ch (+PoE)', label: 'GB/T 50312-2016 Cat 7 Ch (+PoE)' },
{ value: 'GBT 50312-2016 Cat 6A PL no CP', label: 'GB/T 50312-2016 Cat 6A PL no CP' },
{ value: 'GBT 50312-2016 Cat 6A PL no CP (+ALL)', label: 'GB/T 50312-2016 Cat 6A PL no CP (+ALL)' },
{ value: 'GBT 50312-2016 Cat 6A PL no CP (+PoE)', label: 'GB/T 50312-2016 Cat 6A PL no CP (+PoE)' },
{ value: 'GBT 50312-2016 Cat 6A PL with CP', label: 'GB/T 50312-2016 Cat 6A PL with CP' },
{ value: 'GBT 50312-2016 Cat 6A PL with CP (+ALL)', label: 'GB/T 50312-2016 Cat 6A PL with CP (+ALL)' },
{ value: 'GBT 50312-2016 Cat 6A PL with CP (+PoE)', label: 'GB/T 50312-2016 Cat 6A PL with CP (+PoE)' },
{ value: 'GBT 50312-2016 Cat 6A Ch', label: 'GB/T 50312-2016 Cat 6A Ch' },
{ value: 'GBT 50312-2016 Cat 6A Ch (+ALL)', label: 'GB/T 50312-2016 Cat 6A Ch (+ALL)' },
{ value: 'GBT 50312-2016 Cat 6A Ch (+PoE)', label: 'GB/T 50312-2016 Cat 6A Ch (+PoE)' },
{ value: 'GBT 50312-2016 Cat 6 PL', label: 'GB/T 50312-2016 Cat 6 PL' },
{ value: 'GBT 50312-2016 Cat 6 PL (+ALL)', label: 'GB/T 50312-2016 Cat 6 PL (+ALL)' },
{ value: 'GBT 50312-2016 Cat 6 PL (+PoE)', label: 'GB/T 50312-2016 Cat 6 PL (+PoE)' },
{ value: 'GBT 50312-2016 Cat 6 Ch', label: 'GB/T 50312-2016 Cat 6 Ch' },
{ value: 'GBT 50312-2016 Cat 6 Ch (+ALL)', label: 'GB/T 50312-2016 Cat 6 Ch (+ALL)' },
{ value: 'GBT 50312-2016 Cat 6 Ch (+PoE)', label: 'GB/T 50312-2016 Cat 6 Ch (+PoE)' },
{ value: 'GBT 50312-2016 Cat 5e PL', label: 'GB/T 50312-2016 Cat 5e PL' },
{ value: 'GBT 50312-2016 Cat 5e PL (+ALL)', label: 'GB/T 50312-2016 Cat 5e PL (+ALL)' },
{ value: 'GBT 50312-2016 Cat 5e PL (+PoE)', label: 'GB/T 50312-2016 Cat 5e PL (+PoE)' },
{ value: 'GBT 50312-2016 Cat 5e Ch', label: 'GB/T 50312-2016 Cat 5e Ch' },
{ value: 'GBT 50312-2016 Cat 5e Ch (+ALL)', label: 'GB/T 50312-2016 Cat 5e Ch (+ALL)' },
{ value: 'GBT 50312-2016 Cat 5e Ch (+PoE)', label: 'GB/T 50312-2016 Cat 5e Ch (+PoE)' }
]
}
}
},
{
value: '跳线',
label: '跳线',
disabled: true,
children: {
'Cat6A Patch Cords': {
label: 'Cat6A Patch Cords',
disabled: true,
children: []
},
'Cat6 Patch Cords': {
label: 'Cat6 Patch Cords',
disabled: true,
children: []
},
'Cat5e Patch Cords': {
label: 'Cat5e Patch Cords',
disabled: true,
children: []
},
'M12 Patch Cords': {
label: 'M12 Patch Cords',
disabled: true,
children: []
}
}
},
{
value: '应用',
label: '应用',
disabled: false,
children: [
{ value: 'Profinet', label: 'Profinet' },
]
}
]
},
WIRE_ORDER: {
title: '插座配置',
items: [
{ value: 'T568A', label: 'T568A' , disabled: true},
{ value: 'T568B', label: 'T568B' },
{ value: 'Ethernet Two-Pair', label: 'Ethernet Two-Pair' },
{ value: 'M12-D Two-Pair', label: 'M12-D Two-Pair' },
]
},
FIBER_TYPE: {
title: '光纤类型',
subTitle: '电缆组',
items: [
{
value: '定制',
label: '定制',
disabled: true,
children: {}
},
{
value: 'general',
label: '通用',
children: [
{ value: 'OM1 Mulitmode 62.5', label: 'OM1 Mulitmode 62.5' },
{ value: 'OM2 Mulitmode 50', label: 'OM2 Mulitmode 50' },
{ value: 'OM3 Mulitmode 50', label: 'OM3 Mulitmode 50' },
{ value: 'OM4 Mulitmode 50', label: 'OM4 Mulitmode 50' },
{ value: 'OM5 Mulitmode 50', label: 'OM5 Mulitmode 50' },
{ value: 'OS1 Singlemode', label: 'OS1 Singlemode' },
{ value: 'OS2 Singlemode', label: 'OS2 Singlemode' }
]
},
{
value: '制造商',
label: '制造商',
disabled: true,
children: {}
}
]
},
CFP_LIMIT: {
title: '测试极限值',
subTitle: '极限值组',
items: [
{
value: 'TIA',
label: 'TIA',
children: [
{ value: 'TIA-568.3-E Multimode (STD)', label: 'TIA-568.3-E Multimode (STD)' },
{ value: 'TIA-568.3-E Multimode (REF)', label: 'TIA-568.3-E Multimode (REF)' },
{ value: 'TIA-568.3-E Singlemode ISP (STD)', label: 'TIA-568.3-E Singlemode ISP (STD)' },
{ value: 'TIA-568.3-E Singlemode OSP (STD)', label: 'TIA-568.3-E Singlemode OSP (STD)' },
{ value: 'TIA-568.3-E Singlemode ISP (REF)', label: 'TIA-568.3-E Singlemode ISP (REF)' },
{ value: 'TIA-568.3-E Singlemode OSP (REF)', label: 'TIA-568.3-E Singlemode OSP (REF)' }
]
},
{
value: 'China',
label: '中国',
children: [
{ value: 'GB/T 50312-2016 Fiber Link', label: 'GB/T 50312-2016 Fiber Link' },
{ value: 'GB/T 50312-2016 OF-300 Ch', label: 'GB/T 50312-2016 OF-300 Ch' },
{ value: 'GB/T 50312-2016 OF-500 Ch', label: 'GB/T 50312-2016 OF-500 Ch' },
{ value: 'GB/T 50312-2016 OF-2000 Ch', label: 'GB/T 50312-2016 OF-2000 Ch' }
]
},
{
value: 'ISO',
label: 'ISO',
children: [
{ value: 'ISO/IEC 11801-2002 Fibre Link', label: 'ISO/IEC 11801-2002 Fibre Link' },
{ value: 'ISO/IEC 11801-2002 OF-300 CH', label: 'ISO/IEC 11801-2002 OF-300 CH' },
{ value: 'ISO/IEC 11801-2002 OF-500 CH', label: 'ISO/IEC 11801-2002 OF-500 CH' },
{ value: 'ISO/IEC 11801-2002 OF-2000 CH', label: 'ISO/IEC 11801-2002 OF-2000 CH' },
{ value: 'ISO/IEC 14763-3:2024 (Draft)', label: 'ISO/IEC 14763-3:2024 (Draft)' },
{ value: 'ISO/IEC 14763-3:2014', label: 'ISO/IEC 14763-3:2014' },
{ value: 'ISO/IEC 14763-3', label: 'ISO/IEC 14763-3' }
]
}
]
},
OFP_LIMIT: {
title: 'OFP极限值',
items: [
{
value: 'TIA',
label: 'TIA',
children: [
{ value: 'ANSI/TIA-568.3-E', label: 'ANSI/TIA-568.3-E' },
{ value: 'ANSI/TIA-568.3-E RL = 20 dB', label: 'ANSI/TIA-568.3-E RL = 20 dB' },
{ value: 'ANSI/TIA-568.3-E RL = 35 dB', label: 'ANSI/TIA-568.3-E RL = 35 dB' },
{ value: 'ANSI/TIA-568.3-E RL = 55 dB', label: 'ANSI/TIA-568.3-E RL = 55 dB' }
]
},
{
value: 'China',
label: '中国',
children: [
{ value: 'GB/T 50312-2016 Fiber Link', label: 'GB/T 50312-2016 Fiber Link' },
{ value: 'GB/T 50312-2016 OF-300 Ch', label: 'GB/T 50312-2016 OF-300 Ch' },
{ value: 'GB/T 50312-2016 OF-500 Ch', label: 'GB/T 50312-2016 OF-500 Ch' },
{ value: 'GB/T 50312-2016 OF-2000 Ch', label: 'GB/T 50312-2016 OF-2000 Ch' }
]
},
{
value: 'ISO',
label: 'ISO',
children: [
{ value: 'ISO/IEC 11801-2002 Fibre Link', label: 'ISO/IEC 11801-2002 Fibre Link' },
{ value: 'ISO/IEC 11801-2002 OF-300 CH', label: 'ISO/IEC 11801-2002 OF-300 CH' },
{ value: 'ISO/IEC 11801-2002 OF-500 CH', label: 'ISO/IEC 11801-2002 OF-500 CH' },
{ value: 'ISO/IEC 11801-2002 OF-2000 CH', label: 'ISO/IEC 11801-2002 OF-2000 CH' },
{ value: 'ISO/IEC 14763-3:2024 (Draft)', label: 'ISO/IEC 14763-3:2024 (Draft)' },
{ value: 'ISO/IEC 14763-3:2024 RL = 20 (Draft)', label: 'ISO/IEC 14763-3:2024 RL = 20 (Draft)' },
{ value: 'ISO/IEC 14763-3:2024 RL = 35 (Draft)', label: 'ISO/IEC 14763-3:2024 RL = 35 (Draft)' },
{ value: 'ISO/IEC 14763-3:2024 RL = 60 (Draft)', label: 'ISO/IEC 14763-3:2024 RL = 60 (Draft)' },
{ value: 'ISO/IEC 14763-3:2014', label: 'ISO/IEC 14763-3:2014' },
{ value: 'ISO/IEC 14763-3', label: 'ISO/IEC 14763-3' }
]
}
]
}
};
export default function MenuList() {
const {
navigation,
getCurrentProject,
updateProject,
navigateTo,
goBack
} = useDisplayStore();
const currentProject = getCurrentProject();
const { menuType, selectedConfigId } = navigation.current.params;
const [selectedPath, setSelectedPath] = useState([]);
const [currentMenu, setCurrentMenu] = useState(menuData[menuType]);
// 获取当前选中的配置
const currentConfig = currentProject.testConfigs.find(
config => config.id === selectedConfigId
);
// 处理菜单选项选择
const handleMenuSelect = (item) => {
if (item.disabled) return;
if (item.children) {
// 如果有子菜单,更新路径和当前菜单
setSelectedPath([...selectedPath, item]);
setCurrentMenu(item.children);
return;
}
// 如果是最终选项,更新配置并返回
let updatedConfig = { ...currentConfig };
switch (menuType) {
case 'CABLE_TYPE':
updatedConfig.params.cableType = item.value;
break;
case 'LIMIT_VALUE':
updatedConfig.params.limitValue = item.value;
break;
case 'WIRE_ORDER':
updatedConfig.params.wireOrder = item.value;
break;
case 'FIBER_TYPE':
updatedConfig.params.cableType = item.value;
if (updatedConfig.moduleType === 'cfp') {
if (item.value.includes('OM')) {
updatedConfig.params.limitValue = 'TIA-568.3-E Multimode (STD)';
} else {
updatedConfig.params.limitValue = 'TIA-568.3-E Singlemode ISP (STD)';
}
}
break;
case 'CFP_LIMIT':
updatedConfig.params.limitValue = item.value;
break;
case 'OFP_LIMIT':
updatedConfig.params.limitValue = item.value;
break;
case 'MODULE':
// 如果模块类型发生变化,完全替换配置对象
if (currentConfig.moduleType !== item.value) {
switch (item.value) {
case '8000':
updatedConfig = {
id: currentConfig.id,
moduleType: '8000',
modulelable: 'DSX-8000',
params: {
limitValue: 'TIA Cat 6 Channel',
cableType: 'Cat6 U/UTP',
wireOrder: 'T568B'
}
};
break;
case 'cfp':
updatedConfig = {
id: currentConfig.id,
moduleType: 'cfp',
modulelable: 'CertiFiber Pro-Quad',
params: {
limitValue: 'TIA-568.3-E Multimode (STD)',
cableType: 'OM3 Multimode 50',
refJumper: '1',
spliceCount: '0',
connectorCount: '2'
}
};
break;
case 'ofp':
updatedConfig = {
id: currentConfig.id,
moduleType: 'ofp',
modulelable: 'OptiFiber Pro-Quad',
params: {
limitValue: 'General Fiber RL = 35 dB',
cableType: 'OM3 Multimode 50'
}
};
break;
}
} else {
updatedConfig.moduleType = item.value;
updatedConfig.modulelable = item.label;
}
break;
}
// 更新项目数据
const updatedConfigs = currentProject.testConfigs.map(config => {
if (config.id === selectedConfigId) {
return updatedConfig;
}
return config;
});
const projectIndex = useDisplayStore.getState().selectedIndexes.projectIndex;
updateProject(projectIndex, { testConfigs: updatedConfigs });
// 返回上一页
goBack();
};
// 处理返回上一级
const handleBack = () => {
if (selectedPath.length > 0) {
const newPath = selectedPath.slice(0, -1);
setSelectedPath(newPath);
if (newPath.length === 0) {
setCurrentMenu(menuData[menuType]);
} else {
let current = menuData[menuType];
for (const item of newPath) {
current = current.items.find(i => i.value === item.value).children;
}
setCurrentMenu(current);
}
} else {
goBack();
}
};
// 渲染菜单选项
const renderMenuOptions = () => {
if (!currentMenu) return null;
// 如果是子菜单,直接渲染子菜单项
if (Array.isArray(currentMenu)) {
return (
<div className="w-full h-[490px]">
{currentMenu.map((item) => (
<div
key={item.value}
className={`h-[60px] w-full bg-gradient-to-b from-[#e6e3e6] to-[#7b797b] p-4 shadow-lg flex items-center ${
item.disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'
} ${
currentConfig.params.cableType === item.value ? 'bg-blue-500' : ''
}`}
onClick={() => handleMenuSelect(item)}
>
<div className="text-black text-sm">{item.label}</div>
</div>
))}
</div>
);
}
// 如果是对象形式的子菜单(如 LIMIT_VALUE 的二级菜单)
if (typeof currentMenu === 'object' && !currentMenu.items) {
return (
<div className="w-full h-[490px]">
{Object.entries(currentMenu).map(([key, item]) => (
<div
key={key}
className={`h-[60px] w-full bg-gradient-to-b from-[#e6e3e6] to-[#7b797b] p-4 shadow-lg flex items-center cursor-pointer ${
currentConfig.params.cableType === item.value ? 'bg-blue-500' : ''
}`}
onClick={() => handleMenuSelect(item)}
>
<div className="text-black text-sm">{item.label}</div>
</div>
))}
</div>
);
}
// 如果是主菜单,渲染主菜单项
return (
<div className="w-full h-[490px]">
{currentMenu.items.map((item) => (
<div
key={item.value}
className={`h-[60px] w-full bg-gradient-to-b from-[#e6e3e6] to-[#7b797b] p-4 shadow-lg flex items-center ${
item.disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'
} ${
currentConfig.params.cableType === item.value ? 'bg-blue-500' : ''
}`}
onClick={() => handleMenuSelect(item)}
>
<div className="text-black text-sm">{item.label}</div>
</div>
))}
</div>
);
};
// 获取当前菜单标题(返回 null 表示不显示)
const getCurrentTitle = () => {
if (selectedPath.length === 0) {
const subTitle = menuData[menuType]?.subTitle;
return subTitle ? subTitle : null; // 如果 subTitle 为空则返回 null
}
return selectedPath[selectedPath.length - 1].label;
};
return (
<div className="w-full h-full flex flex-col overflow-hidden">
<StatusBar />
<TitleBar
title={menuData[menuType].title}
backTo={selectedPath.length > 0 ? 'menulist' : navigation.previous?.name}
view={selectedPath.length > 0 ? 'setup' : navigation.previous?.view}
onBack={handleBack}
/>
{/* 只有当 getCurrentTitle() 返回值不为 null 时才渲染 SubTitleBar */}
{getCurrentTitle() !== null && (
<SubTitleBar title={getCurrentTitle()} />
)}
<div className="flex-1 bg-[#303040]">
<div className="h-full overflow-y-auto [&::-webkit-scrollbar]:hidden">
{renderMenuOptions()}
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,190 @@
import React, { useState } from 'react';
import StatusBar from '../lib/StatusBar';
import TitleBar from '../lib/TitleBar';
import Keyboard from '../lib/Keyboard';
import useDisplayStore from '@/store/displayStore';
export default function Operators() {
const [inputValue, setInputValue] = useState('');
const [cursorPosition, setCursorPosition] = useState(0);
const [selectedOperators, setSelectedOperators] = useState([]);
const {
getCurrentProject,
setSelectedIndexes,
updateCurrentView,
addOperator,
updateProject,
goBack,
setToastMessage
} = useDisplayStore();
const currentProject = getCurrentProject();
const { view } = useDisplayStore.getState().navigation.current;
const handleDeleteOperators = () => {
if (selectedOperators.length === currentProject.operators.length) {
setToastMessage('至少需要保留一个操作员');
return;
}
// 过滤掉被选中的操作员
const newOperators = currentProject.operators.filter(
operator => !selectedOperators.includes(operator.id)
);
// 更新项目
const currentIndex = useDisplayStore.getState().selectedIndexes.projectIndex;
updateProject(currentIndex, { operators: newOperators });
// 如果当前选中的操作员被删除了重置选中的操作员索引为0
if (selectedOperators.includes(currentProject.operators[useDisplayStore.getState().selectedIndexes.operatorIndex].id)) {
setSelectedIndexes({ operatorIndex: 0 });
}
// 清空选中列表并返回main视图
setSelectedOperators([]);
updateCurrentView('main');
};
const renderContent = () => {
switch (view) {
case 'main':
return (
<div className="flex-1 bg-[#303040] p-0">
<div className="space-y-0">
{currentProject.operators.map((operator, index) => (
<div
key={operator.id}
className={`h-[50px] p-4 shadow-lg flex items-center cursor-pointer ${index === useDisplayStore.getState().selectedIndexes.operatorIndex ? 'bg-blue-500' : 'bg-gradient-to-b from-[#e6e3e6] to-[#7b797b]'}`}
onClick={() => {
setSelectedIndexes({ operatorIndex: index });
goBack();
}}
>
<div className="text-black text-sm">{operator.name}</div>
</div>
))}
</div>
</div>
);
case 'delete':
return (
<div className="flex-1 bg-[#303040] p-0">
<div className="space-y-0">
{currentProject.operators.map((operator) => (
<div
key={operator.id}
className="h-[50px] p-4 shadow-lg flex items-center justify-between bg-gradient-to-b from-[#e6e3e6] to-[#7b797b]"
>
<div className="text-black text-sm">{operator.name}</div>
<input
type="checkbox"
className="w-4 h-4"
checked={selectedOperators.includes(operator.id)}
onChange={(e) => {
if (e.target.checked) {
setSelectedOperators([...selectedOperators, operator.id]);
} else {
setSelectedOperators(selectedOperators.filter(id => id !== operator.id));
}
}}
/>
</div>
))}
</div>
</div>
);
case 'new':
return (
<div className="flex-1 bg-[#303040] p-4 flex flex-col">
<div className="relative">
<input
type="text"
className="w-full h-[50px] bg-[#ffffe1] rounded-sm p-4 text-black"
value={inputValue}
placeholder="请输入操作员名称"
onChange={(e) => {
setInputValue(e.target.value);
setCursorPosition(e.target.selectionStart);
}}
onClick={(e) => {
setCursorPosition(e.target.selectionStart);
}}
onFocus={(e) => {
// 保存光标位置
const cursorPosition = e.target.selectionStart;
e.target.setSelectionRange(cursorPosition, cursorPosition);
}}
/>
</div>
<Keyboard
value={inputValue}
cursorPosition={cursorPosition}
onChange={(newValue, newPosition) => {
setInputValue(newValue);
setCursorPosition(newPosition);
}}
onComplete={() => {
if (inputValue.trim()) {
const newOperator = {
id: Math.random().toString(36).substr(2, 9),
name: inputValue.trim()
};
addOperator(newOperator);
setInputValue('');
updateCurrentView('main');
}
}}
/>
</div>
);
default:
return null;
}
};
return (
<div className="w-full h-full flex flex-col overflow-hidden">
<StatusBar />
<TitleBar
title={
view === 'main' ? "操作员" :
view === 'new' ? "新操作员" :
"删除操作员"
}
backTo={view === 'main' ? goBack : useDisplayStore.getState().navigation.previous?.name || 'home'}
view={useDisplayStore.getState().navigation.previous?.view || 'main'}
/>
{renderContent()}
{view === 'main' && (
<div className="h-[60px] bg-[#303030] flex items-center justify-between px-8">
<button
onClick={() => updateCurrentView('delete')}
className="w-[100px] h-[40px] bg-gradient-to-b from-[#656565] to-[#313431] rounded-sm flex items-center justify-center text-white font-bold shadow-lg"
>
编辑列表
</button>
<button
onClick={() => updateCurrentView('new')}
className="w-[100px] h-[40px] bg-gradient-to-b from-[#656565] to-[#313431] rounded-sm flex items-center justify-center text-white font-bold shadow-lg"
>
新操作员
</button>
</div>
)}
{view === 'delete' && (
<div className="h-[60px] bg-[#303030] flex items-center justify-end px-8">
<button
onClick={handleDeleteOperators}
className="w-[100px] h-[40px] bg-gradient-to-b from-[#656565] to-[#313431] rounded-sm flex items-center justify-center text-white font-bold shadow-lg"
>
删除
</button>
</div>
)}
</div>
);
}

View File

@@ -0,0 +1,629 @@
import React, { useState } from 'react';
import Image from 'next/image';
import StatusBar from '../lib/StatusBar';
import TitleBar from '../lib/TitleBar';
import SubTitleBar from '../lib/SubTitleBar';
import Keyboard from '../lib/Keyboard';
import useDisplayStore from '@/store/displayStore';
export default function Project() {
const [inputValue, setInputValue] = useState('');
const [cursorPosition, setCursorPosition] = useState(0);
const {
getCurrentProject,
navigation,
navigateTo,
updateCurrentView,
goBack,
deleteProject,
setSelectedIndexes,
updateProject,
addProject,
setToastMessage
} = useDisplayStore();
// 获取当前项目数据
const currentProject = getCurrentProject();
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;
const renderContent = () => {
const { view } = navigation.current;
switch (view) {
case 'choose':
return (
<div className="flex-1 bg-[#303040] p-4">
<div className="space-y-2">
{useDisplayStore.getState().projects.map((project, index) => (
<div
key={project.id}
className="h-[50px] bg-gradient-to-b from-[#e6e3e6] to-[#7b797b] rounded-sm p-4 shadow-lg flex justify-between items-center cursor-pointer"
onClick={() => {
setSelectedIndexes({ projectIndex: index });
setSelectedIndexes({ testConfigIndex: 0 });
updateCurrentView('main');
}}
>
<div className="text-black text-sm">{project.name}</div>
<div className="w-3 h-3 relative">
<Image
src="/arrow.png"
alt="箭头"
fill
className="object-contain"
/>
</div>
</div>
))}
</div>
</div>
);
case 'rxtx':
return (
<div className="flex-1 bg-[#303040] p-4">
<button
className="w-full h-[200px] bg-gradient-to-b from-[#e6e3e6] to-[#7b797b] rounded-sm p-4 shadow-lg flex items-center justify-center"
onClick={() => updateCurrentView('rxtx2')}
>
<div className="text-black text-lg">选择存储设备</div>
</button>
</div>
);
case 'rxtx2':
return (
<div className="flex-1 bg-[#303040] p-4 space-y-4">
<button className="w-full h-[100px] bg-gradient-to-b from-[#e6e3e6] to-[#7b797b] rounded-sm p-4 shadow-lg">
<div className="text-black text-lg">导出</div>
</button>
<button className="w-full h-[100px] bg-gradient-to-b from-[#e6e3e6] to-[#7b797b] rounded-sm p-4 shadow-lg">
<div className="text-black text-lg">导入</div>
</button>
</div>
);
case 'setup':
return (
<div className="flex-1 bg-[#303040] p-4 space-y-4">
<button
className="w-full h-[100px] bg-gradient-to-b from-[#e6e3e6] to-[#7b797b] rounded-sm p-4 shadow-lg"
onClick={() => {
if (currentProject?.id === 'default') {
setToastMessage('默认项目不能重命名');
return;
}
setInputValue(currentProject?.name || '');
updateCurrentView('rename');
}}
>
<div className="text-black text-lg">重命名</div>
</button>
<button
className="w-full h-[100px] bg-gradient-to-b from-[#e6e3e6] to-[#7b797b] rounded-sm p-4 shadow-lg"
onClick={() => {
if (currentProject?.id === 'default') {
setToastMessage('默认项目不能删除');
return;
}
// 删除当前项目
deleteProject(useDisplayStore.getState().selectedIndexes.projectIndex);
// 切换到默认项目索引0
setSelectedIndexes({ projectIndex: 0 });
updateCurrentView('main');
}}
>
<div className="text-black text-lg">删除</div>
</button>
</div>
);
case 'rename':
return (
<div className="flex-1 bg-[#303040] p-4 flex flex-col">
<div className="relative">
<input
type="text"
className="w-full h-[50px] bg-[#ffffe1] rounded-sm p-4 text-black mb-4"
value={inputValue}
onChange={(e) => {
setInputValue(e.target.value);
setCursorPosition(e.target.selectionStart);
}}
onClick={(e) => {
setCursorPosition(e.target.selectionStart);
}}
onFocus={(e) => {
const cursorPosition = e.target.selectionStart;
e.target.setSelectionRange(cursorPosition, cursorPosition);
}}
/>
</div>
<Keyboard
value={inputValue}
cursorPosition={cursorPosition}
onChange={(newValue, newPosition) => {
setInputValue(newValue);
setCursorPosition(newPosition);
}}
onComplete={() => {
if (currentProject?.id === 'default') {
setToastMessage('默认项目不能重命名');
setInputValue('');
updateCurrentView('main');
return;
}
if (inputValue.trim()) {
const currentIndex = useDisplayStore.getState().selectedIndexes.projectIndex;
updateProject(currentIndex, { name: inputValue.trim() });
setInputValue('');
updateCurrentView('main');
}
}}
/>
</div>
);
case 'new':
return (
<div className="flex-1 bg-[#303040] p-4 flex flex-col">
<div className="relative">
<input
type="text"
className="w-full h-[50px] bg-[#ffffe1] rounded-sm p-4 text-black mb-4"
value={inputValue}
placeholder="请输入项目名称"
onChange={(e) => {
setInputValue(e.target.value);
setCursorPosition(e.target.selectionStart);
}}
onClick={(e) => {
setCursorPosition(e.target.selectionStart);
}}
onFocus={(e) => {
const cursorPosition = e.target.selectionStart;
e.target.setSelectionRange(cursorPosition, cursorPosition);
}}
/>
</div>
<Keyboard
value={inputValue}
cursorPosition={cursorPosition}
onChange={(newValue, newPosition) => {
setInputValue(newValue);
setCursorPosition(newPosition);
}}
onComplete={() => {
if (inputValue.trim()) {
// 生成唯一ID的函数
const generateId = () => Math.random().toString(36).substr(2, 9);
// 创建新项目
const newProject = {
id: generateId(),
name: inputValue.trim(),
testResults: [],
testConfigs: [{
id: generateId(),
moduleType: '8000',
modulelable: 'DSX-8000',
params: {
limitValue: 'TIA Cat 6 Channel',
cableType: 'Cat6 U/UTP',
wireOrder: 'T568B'
}
}],
cableIds: [{
id: generateId(),
name: '001'
},
{
id: generateId(),
name: '002'
},
],
operators: [{
id: generateId(),
name: 'Bob'
}]
};
// 添加新项目
addProject(newProject);
// 获取新项目的索引
const newIndex = useDisplayStore.getState().projects.length - 1;
// 更新选中索引,重置所有相关索引
setSelectedIndexes({
projectIndex: newIndex,
testConfigIndex: 0,
operatorIndex: 0,
cableIdIndex: 0
});
// 清空输入并更新视图
setInputValue('');
updateCurrentView('main');
}
}}
/>
</div>
);
case 'main':
default:
return (
<div className="flex-1 bg-[#303040] p-4 space-y-4">
{/* 操作员信息 */}
<div
className="h-[50px] bg-gradient-to-b from-[#e6e3e6] to-[#7b797b] rounded-sm p-4 shadow-lg flex justify-between items-center cursor-pointer"
onClick={() => navigateTo('operators', 'main')}
>
<div className="text-black text-sm">操作员: {currentProject?.operators[useDisplayStore.getState().selectedIndexes.operatorIndex]?.name}</div>
<div className="w-3 h-3 relative">
<Image
src="/arrow.png"
alt="箭头"
fill
className="object-contain"
/>
</div>
</div>
{/* 结果统计 */}
<div className="space-y-1">
<div className="text-sm text-gray-400">
结果 {currentProject?.testResults[0]?.date} - {currentProject?.testResults[currentProject.testResults.length - 1]?.date}
</div>
<div
className="h-[50px] bg-gradient-to-b from-[#e6e3e6] to-[#7b797b] rounded-sm p-4 shadow-lg flex justify-between items-center cursor-pointer"
onClick={() => navigateTo('result', 'main')}
>
<div className="flex items-center gap-4">
<div className="flex items-center gap-4">
<div className="flex items-center gap-1">
<span className="text-green-500">{passCount|| 0}</span>
<div className="w-3 h-3 relative">
<Image
src="/pass.png"
alt="通过"
sizes={"auto"}
fill
className="object-contain"
/>
</div>
</div>
<div className="flex items-center gap-1">
<span className="text-red-500">{failCount|| 0}</span>
<div className="w-3 h-3 relative">
<Image
src="/fail.png"
alt="失败"
sizes={"auto"}
fill
className="object-contain"
/>
</div>
</div>
</div>
</div>
<div className="w-3 h-3 relative">
<Image
src="/arrow.png"
alt="箭头"
fill
className="object-contain"
/>
</div>
</div>
</div>
{/* 测试设置 */}
<div className="space-y-1 flex-1 overflow-hidden">
<div className="flex justify-between items-center">
<span className="text-sm text-gray-400">测试设置</span>
<button
className="bg-[#404040] text-white px-3 py-1 rounded-sm text-sm"
onClick={() => {
// 生成唯一ID
const generateId = () => Math.random().toString(36).substr(2, 9);
// 创建新的测试配置
const newConfig = {
id: generateId(),
name: '新测试配置',
moduleType: '8000',
modulelable: 'DSX-8000',
params: {
limitValue: 'TIA Cat 6 Channel',
cableType: 'Cat6 U/UTP',
wireOrder: 'T568B',
refJumper: '1',
spliceCount: '0',
connectorCount: '2'
}
};
// 更新项目,添加新的测试配置
const currentProjectIndex = useDisplayStore.getState().selectedIndexes.projectIndex;
const updatedProject = {
...currentProject,
testConfigs: [...currentProject.testConfigs, newConfig]
};
updateProject(currentProjectIndex, updatedProject);
// 设置新配置为选中状态
const newConfigIndex = updatedProject.testConfigs.length - 1;
setSelectedIndexes({ testConfigIndex: newConfigIndex });
// 跳转到测试配置页面并进入编辑视图
navigateTo('testConfig', 'setup');
}}
>
新测试
</button>
</div>
<div className="h-[100px] overflow-y-auto space-y-2 pr-2">
{currentProject?.testConfigs.map((config, index) => (
<div
key={config.id}
className="h-[100px] bg-gradient-to-b from-[#e6e3e6] to-[#7b797b] rounded-sm p-4 shadow-lg cursor-pointer"
onClick={() => navigateTo('testConfig', 'main')}
>
<div className="flex justify-between">
<div className="space-y-0.2">
{config.moduleType === '8000' && (
<>
<div className="text-black text-sm">{config.params.limitValue}</div>
<div className="text-black text-sm">{config.params.cableType}</div>
<div className="text-black text-sm">{config.params.wireOrder}</div>
</>
)}
{config.moduleType === 'cfp' && (
<>
<div className="text-black text-sm">智能远端</div>
<div className="text-black text-sm">{config.params.limitValue}</div>
<div className="text-black text-sm">{config.params.cableType}</div>
<div className="text-black text-sm">{config.params.refJumper} 跳线参照</div>
</>
)}
{config.moduleType === 'ofp' && (
<>
<div className="text-black text-sm">自动OTDR</div>
<div className="text-black text-sm">{config.params.limitValue}</div>
<div className="text-black text-sm">{config.params.cableType}</div>
</>
)}
</div>
<div className="flex flex-col items-end justify-between">
<span className="text-gray-500 text-sm">{config.modulelable}</span>
<div className="flex gap-2">
<button
className="w-6 h-6 bg-[#404040] rounded-sm flex items-center justify-center"
onClick={(e) => {
e.stopPropagation(); // 阻止事件冒泡
const currentProjectIndex = useDisplayStore.getState().selectedIndexes.projectIndex;
const currentTestConfigIndex = useDisplayStore.getState().selectedIndexes.testConfigIndex;
const selectedConfigId = currentProject.testConfigs[currentTestConfigIndex]?.id;
// 禁止删除最后一项配置
if (currentProject.testConfigs.length <= 1) {
setToastMessage('不能删除最后一项配置');
return;
}
// 更新项目,删除选中的测试配置
const updatedProject = {
...currentProject,
testConfigs: currentProject.testConfigs.filter(
testConfig => testConfig.id !== config.id
)
};
updateProject(currentProjectIndex, updatedProject);
// 更新选中索引
const newIndex = updatedProject.testConfigs.findIndex(
testConfig => testConfig.id === selectedConfigId
);
// 如果删除的是当前选中的配置或者找不到选中的配置ID则设置索引为0
setSelectedIndexes({
testConfigIndex: newIndex === -1 ? 0 : newIndex
});
}}
>
<div className="w-4 h-4 relative">
<Image
src="/delete.png"
alt="删除"
fill
className="object-contain"
/>
</div>
</button>
</div>
</div>
</div>
</div>
))}
</div>
</div>
{/* 电缆ID集 */}
<div className="space-y-1">
<div className="flex justify-between items-center">
<span className="text-sm text-gray-400">电缆ID集</span>
</div>
<div
className="h-[50px] bg-gradient-to-b from-[#e6e3e6] to-[#7b797b] rounded-sm p-4 shadow-lg flex justify-between items-center cursor-pointer"
onClick={() => navigateTo('cableId', 'main')}
>
<div className="text-black text-sm">
下一个ID: {currentProject?.cableIds?.[0]?.name || '001'}
</div>
<div className="w-3 h-3 relative">
<Image
src="/arrow.png"
alt="箭头"
fill
className="object-contain"
/>
</div>
</div>
</div>
</div>
);
}
};
const renderTitleBar = () => {
const { view } = navigation.current;
switch (view) {
case 'choose':
return (
<>
<TitleBar title="更改项目" backTo={null} view="main" />
<SubTitleBar title="选择您要的项目" />
</>
);
case 'rxtx':
return (
<>
<TitleBar title="传输项目" backTo={null} view="main" />
<SubTitleBar title="选择存储设备" />
</>
);
case 'rxtx2':
return (
<>
<TitleBar title="传输项目" backTo={null} view="rxtx" />
<SubTitleBar title="选择所需操作" />
</>
);
case 'setup':
return (
<>
<TitleBar title="管理项目" backTo={null} view="main" />
<SubTitleBar title={currentProject?.name || 'DEFAULT'} />
</>
);
case 'rename':
return (
<>
<TitleBar title="重命名" backTo={null} view="setup" />
</>
);
case 'new':
return (
<>
<TitleBar title="新项目" backTo={null} view="choose" />
</>
);
case 'main':
default:
return (
<>
<TitleBar title="项目" backTo="home" view="main" />
<SubTitleBar title={currentProject?.name || 'DEFAULT'} />
</>
);
}
};
const renderFooter = () => {
const { view } = navigation.current;
switch (view) {
case 'choose':
return (
<div className="h-[60px] bg-[#303030] flex items-center justify-between px-8">
<button
onClick={() => updateCurrentView('new')}
className="w-[100px] h-[40px] bg-gradient-to-b from-[#656565] to-[#313431] rounded-sm flex items-center justify-center text-white font-bold shadow-lg"
>
新项目
</button>
</div>
);
case 'rxtx':
case 'rxtx2':
case 'setup':
case 'rename':
case 'new':
return null;
case 'main':
default:
return (
<div className="h-[60px] bg-[#303030] flex items-center justify-between px-8">
<button
onClick={() => updateCurrentView('choose')}
className="w-[100px] h-[40px] bg-gradient-to-b from-[#656565] to-[#313431] rounded-sm flex items-center justify-center text-white font-bold shadow-lg"
>
更改项目
</button>
<button
onClick={() => updateCurrentView('rxtx')}
className="w-[100px] h-[40px] bg-gradient-to-b from-[#656565] to-[#313431] rounded-sm flex items-center justify-center text-white font-bold shadow-lg"
>
传输
</button>
<button
onClick={() => updateCurrentView('setup')}
className="w-[100px] h-[40px] bg-gradient-to-b from-[#656565] to-[#313431] rounded-sm flex items-center justify-center text-white font-bold shadow-lg"
>
管理
</button>
</div>
);
}
};
return (
<div className="w-full h-full flex flex-col overflow-hidden">
<StatusBar />
{renderTitleBar()}
{renderContent()}
{renderFooter()}
</div>
);
}

View File

@@ -0,0 +1,661 @@
import React, { useState, useEffect } from 'react';
import Image from 'next/image';
import StatusBar from '../lib/StatusBar';
import TitleBar from '../lib/TitleBar';
import SubTitleBar from '../lib/SubTitleBar';
import useDisplayStore from '@/store/displayStore';
import Keyboard from '../lib/Keyboard';
export default function Result() {
const { view } = useDisplayStore.getState().navigation.current;
const { getCurrentProject,setToastMessage } = useDisplayStore();
const currentProject = getCurrentProject();
const [isManageMode, setIsManageMode] = useState(false);
const [selectedResults, setSelectedResults] = useState([]);
const [inputValue, setInputValue] = useState('');
const [showKeyboard, setShowKeyboard] = useState(true);
const [cursorPosition, setCursorPosition] = useState(0);
// 获取当前项目名称
const projectName = currentProject?.name || '';
// 获取测试结果并统计通过/失败数量
const testResults = currentProject?.testResults || [];
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;
// 对测试结果进行排序失败在前通过在后相同结果按name排序数字部分按数值大小排序
const sortedResults = [...testResults].sort((a, b) => {
const aPass = a.resultdata?.result === 'pass';
const bPass = b.resultdata?.result === 'pass';
// 如果通过状态相同则按name排序
if (aPass === bPass) {
// 将名称分割成文本和数字部分
const splitName = (name) => {
const match = name.match(/^(.*?)(\d+)$/);
if (match) {
return {
text: match[1],
number: parseInt(match[2], 10)
};
}
return { text: name, number: 0 };
};
const nameA = splitName(a.name);
const nameB = splitName(b.name);
// 先比较文本部分
if (nameA.text !== nameB.text) {
return nameA.text.localeCompare(nameB.text);
}
// 文本相同则比较数字部分
return nameA.number - nameB.number;
}
return aPass ? 1 : -1;
});
// 处理选择结果
const handleSelect = (result) => {
setSelectedResults(prev => {
if (prev.includes(result.name)) {
return prev.filter(name => name !== result.name);
} else {
return [...prev, result.name];
}
});
};
// 处理删除选中的结果
const handleDelete = () => {
if (selectedResults.length === 0) return;
const newResults = testResults.filter(result => !selectedResults.includes(result.name));
const updatedProject = {
...currentProject,
testResults: newResults
};
const projectIndex = useDisplayStore.getState().projects.findIndex(p => p.id === currentProject.id);
useDisplayStore.getState().updateProject(projectIndex, updatedProject);
setSelectedResults([]);
setIsManageMode(false);
setShowDeleteDialog(false);
};
// 重命名逻辑
useEffect(() => {
if (view === 'rename' && selectedResults.length === 1) {
const selectedResult = testResults.find(result => result.name === selectedResults[0]);
setInputValue(selectedResult.name);
}
}, [view, selectedResults, testResults]);
// 处理重命名完成
const handleRename = () => {
if (!inputValue.trim()) return;
// 检查是否与其他结果名称冲突
const isDuplicate = testResults.some(result =>
result.name !== selectedResults[0] && result.name === inputValue.trim()
);
if (isDuplicate) {
setToastMessage('该名称已存在,请使用其他名称');
return;
}
const newResults = testResults.map(result => {
console.log(testResults);
console.log(result);
if (result.name === selectedResults[0]) {
// 处理CFP模块类型的特殊重命名逻辑
if (result.testconfig?.moduleType === 'cfp') {
const oldName = result.name;
const newName = inputValue.trim();
// 判断旧名字是input还是output
if (oldName === result.inputname) {
// 查找关联结果inputname相同但name不同的结果
const relatedResult = testResults.find(r => r.inputname === result.inputname && r.name !== result.name);
console.log("找到的关联结果:", relatedResult);
// 如果是当前选中的结果更新name和inputname
if (result.name === selectedResults[0]) {
// 更新选中结果的name和inputname
const updatedResult = { ...result, name: newName, inputname: newName };
// 查找并更新关联结果的inputname
const relatedResult = testResults.find(r => r.inputname === result.inputname && r.name !== result.name);
if (relatedResult) {
relatedResult.inputname = newName;
}
return updatedResult;
}
return result;
} else if (oldName === result.outname) {
// 查找关联结果outname相同但name不同的结果
if (result.name === selectedResults[0]) {
// outname
const updatedResult = { ...result, name: newName, outname: newName };
// outname
const relatedResult = testResults.find(r => r.outname === result.outname && r.name !== result.name);
if (relatedResult) {
relatedResult.outname = newName;
}
return updatedResult;
}
return result;
}
}
return { ...result, name: inputValue.trim() };
}
return result;
});
const updatedProject = {
...currentProject,
testResults: newResults
};
const projectIndex = useDisplayStore.getState().projects.findIndex(p => p.id === currentProject.id);
useDisplayStore.getState().updateProject(projectIndex, updatedProject);
useDisplayStore.getState().navigateTo('result', 'main');
// 重置管理状态和选中结果状态
setIsManageMode(false);
setSelectedResults([]);
};
//确认删除提示框
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
//确认移动提示框
const [showConfirmDialog, setShowConfirmDialog] = useState(false);
const [targetProject, setTargetProject] = useState(null);
const [showOverwriteDialog, setShowOverwriteDialog] = useState(false);
const [duplicateResults, setDuplicateResults] = useState([]);
const moveResults = (project, index) => {
// 检查是否选择了结果和目标项目
if (selectedResults.length === 0 || !project) return;
// 检查是否在同一个项目中移动
if (project.id === currentProject.id) {
setToastMessage('不能移动到相同项目');
return;
}
// 检查目标项目中是否有重名结果
const duplicates = selectedResults.filter(resultName =>
project.testResults.some(targetResult => targetResult.name === resultName)
);
if (duplicates.length > 0) {
setDuplicateResults(duplicates);
setTargetProject(project);
setShowOverwriteDialog(true);
} else {
setTargetProject(project);
setShowConfirmDialog(true);
}
};
const handleMove = (overwrite = false) => {
if (!targetProject) return;
// 获取要移动的结果对象
const resultsToMove = testResults.filter(result => selectedResults.includes(result.name));
// 更新目标项目
const updatedTargetResults = overwrite
? [...targetProject.testResults.filter(result => !selectedResults.includes(result.name)), ...resultsToMove]
: [...targetProject.testResults, ...resultsToMove];
const updatedTargetProject = {
...targetProject,
testResults: updatedTargetResults
};
// 更新当前项目
const updatedCurrentResults = testResults.filter(result => !selectedResults.includes(result.name));
const updatedCurrentProject = {
...currentProject,
testResults: updatedCurrentResults
};
// 保存更改
const targetIndex = useDisplayStore.getState().projects.findIndex(p => p.id === targetProject.id);
const currentIndex = useDisplayStore.getState().projects.findIndex(p => p.id === currentProject.id);
useDisplayStore.getState().updateProject(targetIndex, updatedTargetProject);
useDisplayStore.getState().updateProject(currentIndex, updatedCurrentProject);
// 重置状态
setSelectedResults([]);
setIsManageMode(false);
setShowConfirmDialog(false);
setShowOverwriteDialog(false);
setTargetProject(null);
setDuplicateResults([]);
// 显示成功消息并返回主视图
setToastMessage('移动成功');
useDisplayStore.getState().navigateTo('result', 'main');
};
// 渲染标题栏
const renderTitleBar = () => {
const { view } = useDisplayStore.getState().navigation.current;
switch (view) {
case 'rename':
return (
<TitleBar
title="重命名结果"
backTo={useDisplayStore.getState().navigation.previous?.name || 'home'}
view={useDisplayStore.getState().navigation.previous?.view || 'main'}
/>
);
case 'move':
return (
<TitleBar
title="移至"
backTo={useDisplayStore.getState().navigation.previous?.name || 'home'}
view={useDisplayStore.getState().navigation.previous?.view || 'main'}
/>
);
case 'main':
default:
return (
<TitleBar
title="结果"
backTo={'home'}
view={'main'}
/>
);
}
};
// 渲染主要内容
const renderContent = () => {
const { view } = useDisplayStore.getState().navigation.current;
switch (view) {
case 'rename':
return (
<div className="flex-1 bg-[#303040] p-4 flex flex-col">
<div
className="relative mb-4 cursor-pointer"
onClick={() => setShowKeyboard(true)}
>
<input
type="text"
className="w-full h-[50px] bg-[#ffffe1] rounded-sm p-4 text-black"
value={inputValue}
placeholder="请输入测试结果名称"
onChange={(e) => {
setInputValue(e.target.value);
setCursorPosition(e.target.selectionStart);
}}
onClick={(e) => {
setShowKeyboard(true);
setCursorPosition(e.target.selectionStart);
}}
onFocus={(e) => {
const cursorPosition = e.target.selectionStart;
e.target.setSelectionRange(cursorPosition, cursorPosition);
}}
/>
</div>
{showKeyboard && (
<Keyboard
value={inputValue}
cursorPosition={cursorPosition}
onChange={(newValue, newPosition) => {
setInputValue(newValue);
setCursorPosition(newPosition);
}}
onComplete={() => {
setShowKeyboard(false);
}}
/>
)}
</div>
);
case 'move':
return (
<div className="flex-1 bg-[#303040] flex flex-col relative">
<SubTitleBar title="选择您要的项目" />
<div className="space-y-2 p-4">
{useDisplayStore.getState().projects.map((project, index) => (
<div
key={project.id}
className="h-[50px] bg-gradient-to-b from-[#e6e3e6] to-[#7b797b] rounded-sm p-4 shadow-lg flex justify-between items-center cursor-pointer"
onClick={() => {
moveResults(project, index);
}}
>
<div className="text-black text-sm">{project.name}</div>
<div className="w-3 h-3 relative">
<Image
src="/arrow.png"
alt="箭头"
fill
className="object-contain"
/>
</div>
</div>
))}
</div>
</div>
);
case 'main':
default:
return (
<>
<div className="h-[60px] bg-gradient-to-b from-[#b0b0b0] via-[#e0e4e0] to-[#b0b0b0] px-4 flex flex-col justify-center">
<div className="text-black">{projectName}</div>
<div className="flex items-center gap-4">
<div className="flex items-center gap-1">
<span className="text-green-500">{passCount}</span>
<div className="w-3 h-3 relative">
<Image
src="/pass.png"
alt="通过"
sizes={"auto"}
fill
className="object-contain"
/>
</div>
</div>
<div className="flex items-center gap-1">
<span className="text-red-500">{failCount}</span>
<div className="w-3 h-3 relative">
<Image
src="/fail.png"
alt="失败"
sizes={"auto"}
fill
className="object-contain"
/>
</div>
</div>
</div>
</div>
{isManageMode && (
<div className="h-[40px] px-4 bg-[#efebe6] flex items-center border-b border-gray-600">
<div className="flex items-center gap-2">
<input
type="checkbox"
checked={selectedResults.length === sortedResults.length}
onChange={() => {
if (selectedResults.length === sortedResults.length) {
setSelectedResults([]);
} else {
setSelectedResults(sortedResults.map(result => result.name));
}
}}
className="w-4 h-4"
/>
<span className="text-black">选择全部</span>
</div>
</div>
)}
<div className="flex-1 bg-[#303040] overflow-y-auto" style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }}>
<div className="space-y-0">
{sortedResults.map((result, index) => (
<div
key={index}
className="h-[60px] px-4 bg-[#efebe6] flex items-center justify-between border-b border-gray-600 cursor-pointer"
onClick={() => !isManageMode && useDisplayStore.getState().navigateTo('resultinfo', 'save', result)}
>
<div className="flex items-center gap-4">
{isManageMode && (
<input
type="checkbox"
checked={selectedResults.includes(result.name)}
onChange={() => handleSelect(result)}
className="w-4 h-4"
/>
)}
<span className="text-black">{result.name}</span>
</div>
<img
src={
result.CopperResultStatus === 'pass' ||
result.ofpResultStatus === 'pass' ||
(result.name === result.inputname && result.InPortStatus === 'pass') ||
(result.name === result.outname && result.OutPortStatus === 'pass')
? '/pass.png'
: '/fail.png'
}
alt={
result.CopperResultStatus === 'pass' ||
result.ofpResultStatus === 'pass' ||
(result.name === result.inputname && result.InPortStatus === 'pass') ||
(result.name === result.outname && result.OutPortStatus === 'pass')
? '通过'
: '失败'
}
className="w-7 h-7"
/>
</div>
))}
</div>
</div>
</>
);
}
};
// 渲染底部按钮
const renderFooter = () => {
const { view } = useDisplayStore.getState().navigation.current;
switch (view) {
case 'rename':
return (
<div className="h-[60px] bg-[#303030] flex items-center justify-end px-8">
{!showKeyboard && (
<button
onClick={handleRename}
className="w-[100px] h-[40px] bg-gradient-to-b from-[#656565] to-[#313431] rounded-sm flex items-center justify-center text-white font-bold shadow-lg"
>
完成
</button>
)}
</div>
);
case 'move':
return(
<div className="h-[60px] bg-[#303030] flex items-center justify-between px-5">
</div>
);
case 'main':
default:
return (
<div className="h-[60px] bg-[#303030] flex items-center justify-between px-5">
<div className="flex-1">
<button
className="w-[100px] h-[40px] bg-gradient-to-b from-[#656565] to-[#313431] rounded-sm flex items-center justify-center text-white font-bold shadow-lg"
onClick={() => {
if (selectedResults.length > 0) {
useDisplayStore.getState().navigateTo('result', 'move');
}
}}
disabled={selectedResults.length === 0}
style={{ opacity: selectedResults.length > 0 ? 1 : 0.5 }}
>
移动
</button>
</div>
<div className="flex-1 flex justify-center">
<button
className="w-[100px] h-[40px] bg-gradient-to-b from-[#656565] to-[#313431] rounded-sm flex items-center justify-center text-white font-bold shadow-lg"
onClick={() => {
if (selectedResults.length === 1) {
const selectedResult = testResults.find(result => result.name === selectedResults[0]);
useDisplayStore.getState().navigateTo('result', 'rename', selectedResult);
}
}}
disabled={selectedResults.length !== 1}
style={{ opacity: selectedResults.length === 1 ? 1 : 0.5 }}
>
重命名
</button>
</div>
<div className="flex-1 flex justify-end">
<button
className="w-[100px] h-[40px] bg-gradient-to-b from-[#656565] to-[#313431] rounded-sm flex items-center justify-center text-white font-bold shadow-lg"
onClick={() => isManageMode ? setShowDeleteDialog(true) : setIsManageMode(true)}
>
{isManageMode ? '删除' : '管理'}
</button>
</div>
</div>
);
}
};
return (
<div className="w-full h-full flex flex-col overflow-hidden">
<StatusBar />
{renderTitleBar()}
{renderContent()}
{renderFooter()}
{/* 确认删除对话框 */}
{showDeleteDialog && (
<div className="w-[480px] h-[640px] bg-[#002842d4] absolute z-9999">
<div className='pl-10 pt-60'>
<div className="bg-[#2B3C5B] rounded-lg p-6 w-[400px] min-h-[200px] flex flex-col">
<h3 className="text-white text-xl font-bold mb-4">提示</h3>
<div className="flex-1 flex items-center justify-center">
<div className="text-white text-lg">是否删除选中的测试结果</div>
</div>
<div className="flex justify-center mt-4">
<div className="flex justify-center gap-10">
<button
className="text-white text-s font-bold px-4 py-2 bg-[#1d3155] rounded-md hover:bg-[#2b4466]"
onClick={() => setShowDeleteDialog(false)}
>
取消
</button>
<button
className="text-white text-s font-bold px-4 py-2 bg-[#1d3155] rounded-md hover:bg-[#2b4466]"
onClick={() => handleDelete()}
>
确认
</button>
</div>
</div>
</div>
</div>
</div>
)}
{/* 确认移动对话框 */}
{showConfirmDialog && (
<div className="w-[480px] h-[640px] bg-[#002842d4] absolute z-9999">
<div className='pl-10 pt-60'>
<div className="bg-[#2B3C5B] rounded-lg p-6 w-[400px] min-h-[200px] flex flex-col">
<h3 className="text-white text-xl font-bold mb-4">提示</h3>
<div className="flex-1 flex items-center justify-center">
<div className="text-white text-lg">是否将选中的测试结果移动到项目 {targetProject?.name}</div>
</div>
<div className="flex justify-center mt-4">
<div className="flex justify-center gap-10">
<button
className=" text-white text-s font-bold px-4 py-2 bg-[#1d3155] rounded-md hover:bg-[#2b4466]"
onClick={() => setShowConfirmDialog(false)}
>
取消
</button>
<button
className=" text-white text-s font-bold px-4 py-2 bg-[#1d3155] rounded-md hover:bg-[#2b4466]"
onClick={() => handleMove(false)}
>
确认
</button>
</div>
</div>
</div>
</div>
</div>
)}
{/* 重名覆盖确认对话框 */}
{showOverwriteDialog && (
<div className="w-[480px] h-[640px] bg-[#002842d4] absolute z-9999">
<div className='pl-10 pt-60'>
<div className="bg-[#2B3C5B] rounded-lg p-6 w-[400px] min-h-[200px] flex flex-col">
<h3 className="text-white text-xl font-bold mb-4">提示</h3>
<div className="flex-1 flex flex-col items-center justify-center">
<div className="text-white text-lg mb-4">以下测试结果在目标项目中已存在</div>
<ul className="mb-4 list-disc pl-6">
{duplicateResults.map((name, index) => (
<li key={index} className="text-red-500">{name}</li>
))}
</ul>
<div className="text-white text-lg">是否覆盖这些结果</div>
</div>
<div className="flex justify-center mt-4">
<div className="flex justify-center gap-10">
<button
className="text-white text-s font-bold px-4 py-2 bg-[#1d3155] rounded-md hover:bg-[#2b4466]"
onClick={() => {
setShowOverwriteDialog(false);
setTargetProject(null);
setDuplicateResults([]);
}}
>
取消
</button>
<button
className="text-white text-s font-bold px-4 py-2 bg-[#1d3155] rounded-md hover:bg-[#2b4466]"
onClick={() => handleMove(true)}
>
覆盖
</button>
</div>
</div>
</div>
</div>
</div>
)}
</div>
);
}

View File

@@ -0,0 +1,658 @@
import React, { useState, useEffect } from 'react';
import StatusBar from '../lib/StatusBar';
import ResultTitleBar from '../lib/ResultTitleBar';
import TitleBar from '../lib/TitleBar';
import CopperResultMain from '../lib/CopperResultMain';
import OLTSResultMain from '../lib/OLTSResultMain';
import OTDRResultMain from '../lib/OTDRResultMain';
import Keyboard from '../lib/Keyboard';
import useDisplayStore from '@/store/displayStore';
// 初始化测试结果音效对象
const testPassSound = typeof Audio !== 'undefined' ? new Audio('/sounds/test_pass.wav') : null;
const testFailSound = typeof Audio !== 'undefined' ? new Audio('/sounds/test_fail.wav') : null;
// 自定义确认弹窗组件
const ConfirmDialog = ({ message, onConfirm, onCancel }) => (
<div className="w-[480px] h-[640px] bg-[#002842d4] absolute z-[9999] top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2">
<div className='pl-10 pt-60'>
<div className="bg-[#2B3C5B] rounded-lg p-6 w-[400px] min-h-[200px] flex flex-col">
<h3 className="text-white text-xl font-bold mb-4">提示</h3>
<div className="flex-1 flex items-center justify-center">
<div className="text-white text-lg">{message}</div>
</div>
<div className="flex justify-center gap-4 mt-4">
<button
onClick={onConfirm}
className="bg-[#354e7a] text-white px-6 py-2 rounded hover:bg-[#1E293B] transition-colors"
>
确定
</button>
<button
onClick={onCancel}
className="bg-[#4a4a4a] text-white px-6 py-2 rounded hover:bg-[#3a3a3a] transition-colors"
>
取消
</button>
</div>
</div>
</div>
</div>
);
export default function ResultInfo() {
const [showKeyboard, setShowKeyboard] = useState(true);
const [cursorPosition, setCursorPosition] = useState(0);
const [inputValue, setInputValue] = useState('');
const [inputValue2, setInputValue2] = useState('');
const [activeInput, setActiveInput] = useState(1); // 1 表示第一个输入框2 表示第二个输入框
const [showConfirmDialog, setShowConfirmDialog] = useState(false);
const [confirmDialogMessage, setConfirmDialogMessage] = useState('');
const [confirmDialogCallback, setConfirmDialogCallback] = useState(null);
const { navigation } = useDisplayStore.getState();
const tempTestResult = navigation.current.params;
// 根据测试结果播放音效
const { hasPlayedSound } = useDisplayStore.getState();
useEffect(() => {
// 只有从测试页面进入nosave视图时才播放声音
if (navigation.previous.name === 'testing' &&
tempTestResult &&
!hasPlayedSound) {
if (tempTestResult.CopperResultStatus === 'pass'||tempTestResult.CFPResultStatus === 'pass' ||tempTestResult.ofpResultStatus === 'pass') {
testPassSound?.play().catch(console.error);
} else {
testFailSound?.play().catch(console.error);
}
useDisplayStore.setState({ hasPlayedSound: true });
}
}, [navigation, tempTestResult, hasPlayedSound]);
// 从URL参数中获取临时测试结果
const {
getCurrentProject,
getCurrentCableId,
getCurrentCableId2,
updateCurrentView,
getCurrentTestConfig,
navigateTo,
setToastMessage,
updateProject
} = useDisplayStore();
const currentProject = getCurrentProject();
const { view } = useDisplayStore.getState().navigation.current;
// 获取当前的线缆ID
const currentCableId = getCurrentCableId().name || '';
const currentCableId2 = getCurrentCableId2().name || '';
// 计算下一个序号的ID
const getNextId = (currentId) => {
if (!currentId) return '';
// 检查是否以数字结尾
const numMatch = currentId.match(/^(.*?)(\d+)$/);
if (numMatch) {
const [, prefix, num] = numMatch;
const nextNum = String(Number(num) + 1).padStart(num.length, '0');
return prefix + nextNum;
}
// 检查是否以字母结尾
const letterMatch = currentId.match(/^(.*?)([a-zA-Z]+)$/);
if (letterMatch) {
const [, prefix, letters] = letterMatch;
// 将字母转换为数组以便处理
const letterArray = letters.split('');
let carry = true;
// 从右向左处理每个字母
for (let i = letterArray.length - 1; i >= 0 && carry; i--) {
if (letterArray[i] === 'z') {
letterArray[i] = 'a';
carry = true;
} else if (letterArray[i] === 'Z') {
letterArray[i] = 'A';
carry = true;
} else {
letterArray[i] = String.fromCharCode(letterArray[i].charCodeAt(0) + 1);
carry = false;
}
}
// 如果还有进位,说明需要在前面添加一个字母
if (carry) {
if (letters[0] >= 'a' && letters[0] <= 'z') {
letterArray.unshift('a');
} else {
letterArray.unshift('A');
}
}
return prefix + letterArray.join('');
}
// 如果既不是数字也不是字母结尾,直接返回原值
return currentId;
};
// 初始化输入值为当前的线缆ID
useEffect(() => {
setInputValue(currentCableId);
setInputValue2(currentCableId2);
}, [currentCableId, currentCableId2]);
// 当第一个输入框值变化时,仅在用户手动输入时自动更新第二个输入框
useEffect(() => {
if (tempTestResult?.testconfig?.moduleType === 'cfp' && inputValue && inputValue !== currentCableId) {
setInputValue2(getNextId(inputValue));
}
}, [inputValue, view, currentCableId]);
//创建保存结果存储逻辑
const handleComplete = () => {
const currentId = inputValue.trim();
// 检查是否存在重名的测试结果
const currentProject = getCurrentProject();
const existingResults = currentProject?.testResults || [];
// 检查两个ID是否重复
const isDuplicate = existingResults.some(result => result.name === currentId);
// 重名替换
if (isDuplicate) {
setConfirmDialogMessage('该线缆ID已使用是否覆盖');
setConfirmDialogCallback(() => () => {
// 更新临时测试结果的名称
const updatedTestResult = {
...tempTestResult,
name: currentId
};
// 更新当前项目的测试结果
const currentIndex = useDisplayStore.getState().projects.findIndex(p => p === currentProject);
if (currentIndex !== -1) {
// 移除旧的测试结果并添加新的
const newResults = existingResults.filter(result => result.name !== currentId);
updateProject(currentIndex, {
testResults: [...newResults, updatedTestResult]
});
// 更新navigation.current.params中的测试结果名称
useDisplayStore.setState({
navigation: {
...navigation,
current: {
...navigation.current,
params: updatedTestResult
}
}
});
}
updateCurrentView('save');
setShowConfirmDialog(false);
});
setShowConfirmDialog(true);
return;
}
// 更新临时测试结果的名称并保存到项目中
if (tempTestResult) {
const updatedTestResult = {
...tempTestResult,
name: currentId
};
// 更新当前项目的cableIds.name为下一个ID
const currentIndex = useDisplayStore.getState().projects.findIndex(p => p === currentProject);
if (currentIndex !== -1) {
//更新测试结果
updateProject(currentIndex, {
testResults: [...(currentProject.testResults || []), updatedTestResult]
});
// 更新navigation.current.params中的测试结果名称
const navigation = useDisplayStore.getState().navigation;
useDisplayStore.setState({
navigation: {
...navigation,
current: {
...navigation.current,
params: updatedTestResult
}
}
});
// 获取下一个ID
const nextId = (() => {
const currentId = inputValue.trim();
if (!currentId) return currentId;
// 获取最后一个字符
const lastChar = currentId.slice(-1);
const prefix = currentId.slice(0, -1);
// 如果最后一个字符是数字
if (/\d/.test(lastChar)) {
const match = currentId.match(/^(.*?)(\d+)$/);
if (match) {
const numPrefix = match[1];
const number = parseInt(match[2]) + 1;
return `${numPrefix}${number.toString().padStart(match[2].length, '0')}`;
}
}
// 如果最后一个字符是字母
if (/[A-Za-z]/.test(lastChar)) {
const nextChar = String.fromCharCode(lastChar.charCodeAt(0) + 1);
// 如果超过Z或z回到A或a
if ((lastChar === 'Z' && nextChar > 'Z') || (lastChar === 'z' && nextChar > 'z')) {
const baseChar = lastChar === 'Z' ? 'A' : 'a';
return `${prefix}${baseChar}`;
}
return `${prefix}${nextChar}`;
}
return currentId;
})();
// 获取当前项目的所有cableIds
const currentCableIds = currentProject?.cableIds || [];
const selectedId = getCurrentCableId().id;
// 只更新选中的ID保留其他ID不变
const updatedCableIds = currentCableIds.map(cable =>
cable.id === selectedId ? { ...cable, name: nextId } : cable
);
// 更新项目
updateProject(currentIndex, {
cableIds: updatedCableIds
});
}
updateCurrentView('save');
}
}
const handleComplete2 = () => {
const currentId = inputValue.trim();
const currentId2 = inputValue2.trim();
// 检查两个ID是否相同
if (currentId === currentId2) {
setConfirmDialogMessage('输入输出ID不能相同请检查');
setConfirmDialogCallback(() => () => {
setShowConfirmDialog(false);
});
setShowConfirmDialog(true);
return;
}
// 检查是否存在重名的测试结果
const currentProject = getCurrentProject();
const existingResults = currentProject?.testResults || [];
// 检查两个ID是否重复
const isDuplicate1 = existingResults.some(result => result.name === currentId);
const isDuplicate2 = existingResults.some(result => result.name === currentId2);
const currentConfig = getCurrentTestConfig();
const cableType = currentConfig.params.cableType;
const isMultiMode = cableType.includes('OM');
if (isDuplicate1 || isDuplicate2) {
const message = [];
if (isDuplicate1) message.push(`线缆ID ${currentId}`);
if (isDuplicate2) message.push(`线缆ID ${currentId2}`);
setConfirmDialogMessage(`${message.join(' 和 ')}已使用,是否覆盖?`);
setConfirmDialogCallback(() => () => {
// 更新临时测试结果的名称
const updatedTestResult1 = {
...tempTestResult,
name: currentId,
inputname: isMultiMode ? currentId2 : currentId,
outname: isMultiMode ? currentId : currentId2,
};
const updatedTestResult2 = {
...tempTestResult,
name: currentId2,
inputname: isMultiMode ? currentId2 : currentId,
outname: isMultiMode ? currentId : currentId2,
};
// 更新当前项目的测试结果
const currentIndex = useDisplayStore.getState().projects.findIndex(p => p === currentProject);
if (currentIndex !== -1) {
// 移除旧的测试结果并添加新的
const newResults = existingResults.filter(result =>
result.name !== currentId && result.name !== currentId2
);
updateProject(currentIndex, {
testResults: [...newResults, updatedTestResult1, updatedTestResult2]
});
// 更新navigation.current.params中的测试结果名称
useDisplayStore.setState({
navigation: {
...navigation,
current: {
...navigation.current,
params: updatedTestResult1
}
}
});
}
updateCurrentView('save');
setShowConfirmDialog(false);
});
setShowConfirmDialog(true);
return;
}
// 更新临时测试结果的名称并保存到项目中
if (tempTestResult) {
// 创建两个测试结果
const updatedTestResult1 = {
...tempTestResult,
name: currentId,
inputname: isMultiMode ? currentId2 : currentId,
outname: isMultiMode ? currentId : currentId2,
};
const updatedTestResult2 = {
...tempTestResult,
name: currentId2,
inputname: isMultiMode ? currentId2 : currentId,
outname: isMultiMode ? currentId : currentId2,
};
// 更新当前项目的cableIds.name为下一个ID
const currentIndex = useDisplayStore.getState().projects.findIndex(p => p === currentProject);
if (currentIndex !== -1) {
//更新测试结果
updateProject(currentIndex, {
testResults: [...(currentProject.testResults || []), updatedTestResult1,updatedTestResult2]
});
// 更新navigation.current.params中的测试结果名称
const navigation = useDisplayStore.getState().navigation;
useDisplayStore.setState({
navigation: {
...navigation,
current: {
...navigation.current,
params: updatedTestResult1
}
}
});
// 获取下一个ID
const nextId = getNextId(getNextId(inputValue.trim()));
// 获取下一个ID2
const nextId2 = getNextId(getNextId(inputValue2.trim()));
// 获取当前项目的所有cableIds
const currentCableIds = currentProject?.cableIds || [];
const selectedId = getCurrentCableId().id;
const selectedId2 = getCurrentCableId2().id;
// 只更新选中的ID保留其他ID不变
const updatedCableIds = currentCableIds.map(cable =>
cable.id === selectedId ? { ...cable, name: nextId } :
cable.id === selectedId2 ? { ...cable, name: nextId2 } :
cable
);
// 更新项目,添加两个测试结果
updateProject(currentIndex, {
cableIds: updatedCableIds,
});
}
updateCurrentView('save');
}
}
// 创建测试结果的视图
const renderContent = () => {
const renderResultMain = () => {
const moduleType = tempTestResult?.testconfig?.moduleType;
switch (moduleType) {
case '8000':
return <CopperResultMain testResult={tempTestResult} />;
case 'cfp':
return <OLTSResultMain testResult={tempTestResult} />;
case 'ofp':
return <OTDRResultMain testResult={tempTestResult} />;
default:
return <CopperResultMain testResult={tempTestResult} />;
}
};
const renderSetName = () => {
const moduleType = tempTestResult?.testconfig?.moduleType;
switch (moduleType) {
case 'cfp':
return(
<div className="flex-1 bg-[#303040] p-4 flex flex-col">
<div className="mb-8">
<div className="mb-1 text-white text-sm">输出光纤ID1</div>
<div className="relative cursor-pointer">
<input
type="text"
className="w-full h-[50px] bg-[#ffffe1] rounded-sm px-4 text-black overflow-x-auto whitespace-nowrap"
value={inputValue}
placeholder="请输入线缆ID1"
onChange={(e) => {
setInputValue(e.target.value);
setCursorPosition(e.target.selectionStart);
}}
onClick={(e) => {
setActiveInput(1);
setShowKeyboard(true);
setCursorPosition(e.target.selectionStart);
}}
onFocus={(e) => {
const cursorPosition = e.target.selectionStart;
e.target.setSelectionRange(cursorPosition, cursorPosition);
}}
/>
</div>
</div>
<div>
<div className="mb-1 text-white text-sm">输入光纤ID2</div>
<div className="relative cursor-pointer">
<input
type="text"
className="w-full h-[50px] bg-[#ffffe1] rounded-sm px-4 text-black overflow-x-auto whitespace-nowrap"
value={inputValue2}
placeholder="请输入线缆ID2"
onChange={(e) => {
setInputValue2(e.target.value);
setCursorPosition(e.target.selectionStart);
}}
onClick={(e) => {
setActiveInput(2);
setShowKeyboard(true);
setCursorPosition(e.target.selectionStart);
}}
onFocus={(e) => {
const cursorPosition = e.target.selectionStart;
e.target.setSelectionRange(cursorPosition, cursorPosition);
}}
/>
</div>
</div>
{showKeyboard && (
<Keyboard
value={activeInput === 1 ? inputValue : inputValue2}
cursorPosition={cursorPosition}
onChange={(newValue, newPosition) => {
if (activeInput === 1) {
setInputValue(newValue);
} else {
setInputValue2(newValue);
}
setCursorPosition(newPosition);
}}
onComplete={() => {
setShowKeyboard(false);
}}
/>
)}
</div>
);
default:
return (
<div className="flex-1 bg-[#303040] p-4 flex flex-col">
<div
className="relative mb-4 cursor-pointer"
onClick={() => setShowKeyboard(true)}
>
<input
type="text"
className="w-full h-[50px] bg-[#ffffe1] rounded-sm p-4 text-black"
value={inputValue}
placeholder="请输入线缆ID"
onChange={(e) => {
setInputValue(e.target.value);
setCursorPosition(e.target.selectionStart);
}}
onClick={(e) => {
setShowKeyboard(true);
setCursorPosition(e.target.selectionStart);
}}
onFocus={(e) => {
// 保存光标位置
const cursorPosition = e.target.selectionStart;
e.target.setSelectionRange(cursorPosition, cursorPosition);
}}
/>
</div>
{showKeyboard && (
<Keyboard
value={inputValue}
cursorPosition={cursorPosition}
onChange={(newValue, newPosition) => {
setInputValue(newValue);
setCursorPosition(newPosition);
}}
onComplete={() => {
setShowKeyboard(false);
}}
/>
)}
</div>
);
}
};
switch (view) {
case 'nosave':
return (
<div className="w-full h-full flex flex-col overflow-hidden">
<StatusBar />
{tempTestResult?.testconfig?.moduleType !== 'cfp' ? (
<ResultTitleBar title="未保存结果" testResult={tempTestResult} backTo="home" view="main" />
) : (<ResultTitleBar title=" " testResult={tempTestResult} backTo="home" view="main" />
)}
{renderResultMain()}
<div className="h-[60px] bg-[#303030] flex items-center justify-end px-4">
<button
onClick={() => updateCurrentView('setname')}
className="w-[100px] h-[40px] bg-gradient-to-b from-[#ffd773] to-[#e7aa29] rounded-sm flex items-center justify-center text-black font-bold shadow-lg"
>
保存
</button>
</div>
</div>
);
case 'setname':
return (
<div className="w-full h-full flex flex-col overflow-hidden">
<StatusBar />
<TitleBar
title="保存结果"
backTo={useDisplayStore.getState().navigation.previous?.name || 'home'}
view={useDisplayStore.getState().navigation.previous?.view || 'main'}
/>
<div className="flex-1 bg-[#303040] p-2 flex flex-col">
{renderSetName()}
</div>
<div className="h-[60px] bg-[#303030] flex items-center justify-end px-4">
{tempTestResult?.testconfig?.moduleType === "cfp" ? (
!showKeyboard && (
<button
onClick={() => handleComplete2()}
className="w-[100px] h-[40px] bg-gradient-to-b from-[#ffd773] to-[#e7aa29] rounded-sm flex items-center justify-center text-black font-bold shadow-lg"
>
保存
</button>
)
) : (
!showKeyboard && (<button
onClick={() => handleComplete()}
className="w-[100px] h-[40px] bg-gradient-to-b from-[#ffd773] to-[#e7aa29] rounded-sm flex items-center justify-center text-black font-bold shadow-lg"
>
保存
</button>
)
)}
</div>
</div>
);
case 'save':
return (
<div className="w-full h-full flex flex-col overflow-hidden">
<StatusBar />
{tempTestResult?.testconfig?.moduleType !== 'cfp' ? (
<ResultTitleBar testResult={tempTestResult} backTo="result" view="main" />
) : (<ResultTitleBar title=" " testResult={tempTestResult} backTo="result" view="main" />
)}
{renderResultMain()}
<div className="h-[60px] bg-[#303030] flex items-center justify-end px-4">
<button
onClick={() => {
navigateTo('home', 'main');
}}
className="w-[100px] h-[40px] bg-gradient-to-b from-[#ffd773] to-[#e7aa29] rounded-sm flex items-center justify-center text-black font-bold shadow-lg"
>
主页
</button>
</div>
</div>
);
default:
return null;
}
};
const content = renderContent();
return (
<div className="relative w-full h-full">
{content}
{showConfirmDialog && (
<ConfirmDialog
message={confirmDialogMessage}
onConfirm={confirmDialogCallback}
onCancel={() => setShowConfirmDialog(false)}
/>
)}
</div>
);
}

View File

@@ -0,0 +1,660 @@
import React, { useState, useEffect } from 'react';
import Image from 'next/image';
import StatusBar from '../lib/StatusBar';
import TitleBar from '../lib/TitleBar';
import SubTitleBar from '../lib/SubTitleBar';
import useDisplayStore from '@/store/displayStore';
import { v4 as uuidv4 } from 'uuid';
import MenuList from './MenuList.js';
export default function TestConfig() {
const [selectedConfigId, setSelectedConfigId] = useState(null);
const {
getCurrentProject,
navigation,
navigateTo,
updateCurrentView,
goBack,
setSelectedIndexes,
updateProject,
addProject,
setToastMessage
} = useDisplayStore();
// 获取当前项目数据
const currentProject = getCurrentProject();
// 单多模判断
const { getCurrentTestConfig } = useDisplayStore.getState();
const currentConfig = getCurrentTestConfig();
const cableType = currentConfig.params.cableType;
const isMultiMode = cableType.includes('OM');
// 设置默认选中的配置
useEffect(() => {
// 优先使用导航参数中的配置ID
const configIdFromParams = navigation.current.params?.selectedConfigId;
if (configIdFromParams) {
setSelectedConfigId(configIdFromParams);
return;
}
// 如果没有参数,使用当前选中的配置索引
const currentTestConfigIndex = useDisplayStore.getState().selectedIndexes.testConfigIndex;
if (currentProject?.testConfigs && currentTestConfigIndex >= 0) {
setSelectedConfigId(currentProject.testConfigs[currentTestConfigIndex].id);
}
// 确保导航历史正确记录
if (navigation.current.name === 'testconfig' && navigation.current.view === 'main') {
const current = navigation.current;
const previous = navigation.previous;
if (!previous || previous.name !== 'testconfig' || previous.view !== 'main') {
useDisplayStore.getState().navigateTo('testconfig', 'main', current.params);
}
}
}, [currentProject, navigation.current.params]);
// 渲染主视图内容
const renderMainContent = () => {
return (
<div className="h-[490px] flex-1 bg-[#303040] p-4">
<div className="h-full overflow-y-auto space-y-2 pr-2">
{currentProject?.testConfigs.map((config) => (
<div
key={config.id}
className="h-[100px] bg-gradient-to-b from-[#e6e3e6] to-[#7b797b] rounded-sm p-4 shadow-lg flex items-center gap-4 cursor-pointer"
onClick={() => {
setSelectedConfigId(config.id);
// 保存选中的配置ID到导航参数
updateCurrentView('main', { selectedConfigId: config.id });
}}
>
<div className="w-6 h-6 border-2 border-gray-600 rounded-full flex items-center justify-center">
{selectedConfigId === config.id && (
<div className="w-4 h-4 bg-blue-500 rounded-full" />
)}
</div>
<div className="flex-1 relative">
<div className="mt-1">
{config.moduleType === '8000' && (
<>
<div className="text-black text-sm">{config.params.limitValue}</div>
<div className="text-black text-sm">{config.params.cableType}</div>
<div className="text-black text-sm">{config.params.wireOrder}</div>
</>
)}
{config.moduleType === 'cfp' && (
<>
<div className="text-black text-sm">智能远端</div>
<div className="text-black text-sm">{config.params.limitValue}</div>
<div className="text-black text-sm">{config.params.cableType}</div>
<div className="text-black text-sm">{config.params.refJumper} 跳线参照</div>
</>
)}
{config.moduleType === 'ofp' && (
<>
<div className="text-black text-sm">自动OTDR</div>
<div className="text-black text-sm">{config.params.limitValue}</div>
<div className="text-black text-sm">{config.params.cableType}</div>
</>
)}
</div>
<div className="text-gray-400 text-sm absolute top-0 right-0">{config.modulelable}</div>
</div>
<div className="w-3 h-3 relative">
<Image
src="/arrow.png"
alt="箭头"
fill
className="object-contain"
/>
</div>
</div>
))}
</div>
</div>
);
};
// 渲染设置视图内容
const renderSetupContent = () => {
const currentConfig = currentProject?.testConfigs.find(
config => config.id === selectedConfigId
);
if (!currentConfig) return null;
const renderConfigContent = () => {
switch (currentConfig.moduleType) {
case '8000':
return (
<div className="space-y-2">
<div className="space-y-0">
<div
className="h-[50px] bg-gradient-to-b from-[#e6e3e6] to-[#7b797b] rounded-t-sm p-4 shadow-lg flex justify-between items-center cursor-pointer"
onClick={() => navigateTo('menulist', 'setup', {
menuType: 'CABLE_TYPE',
selectedConfigId: selectedConfigId,
backTo: 'testconfig',
backView: 'setup'
})}
>
<div className="text-black text-l font-bold">电缆类型{currentConfig.params.cableType}</div>
<div className="w-3 h-3 relative">
<Image src="/arrow.png" alt="箭头" fill className="object-contain" />
</div>
</div>
<div className="h-[50px] bg-gradient-to-b from-[#ffffff] to-[#ffffff] rounded-b-sm p-4 shadow-lg flex justify-between items-center">
<div className="text-black text-l">NVP根据电缆类型生成</div>
<div className="w-3 h-3 relative">
<Image src="/arrow.png" alt="箭头" fill className="object-contain" />
</div>
</div>
</div>
<div className="space-y-0">
<div
className="h-[50px] bg-gradient-to-b from-[#e6e3e6] to-[#7b797b] rounded-t-sm p-4 shadow-lg flex justify-between items-center cursor-pointer"
onClick={() => navigateTo('menulist', 'setup', {
menuType: 'LIMIT_VALUE',
selectedConfigId: selectedConfigId,
backTo: 'testconfig',
backView: 'setup'
})}
>
<div className="text-black text-l font-bold">测试极限值{currentConfig.params.limitValue}</div>
<div className="w-3 h-3 relative">
<Image src="/arrow.png" alt="箭头" fill className="object-contain" />
</div>
</div>
<div className="h-[50px] bg-gradient-to-b from-[#ffffff] to-[#ffffff] p-4 shadow-lg flex justify-between items-center">
<div className="text-black text-l">存储绘图数据</div>
<div className="w-3 h-3 relative">
<Image src="/arrow.png" alt="箭头" fill className="object-contain" />
</div>
</div>
<div className="h-[50px] bg-gradient-to-b from-[#ffffff] to-[#ffffff] rounded-b-sm p-4 shadow-lg flex justify-between items-center">
<div className="text-black text-l">HDTDR/HDTDX仅限失败/通过</div>
<div className="w-3 h-3 relative">
<Image src="/arrow.png" alt="箭头" fill className="object-contain" />
</div>
</div>
</div>
<div
className="h-[50px] bg-gradient-to-b from-[#e6e3e6] to-[#7b797b] rounded-sm p-4 shadow-lg flex justify-between items-center cursor-pointer"
onClick={() => navigateTo('menulist', 'setup', {
menuType: 'WIRE_ORDER',
selectedConfigId: selectedConfigId,
backTo: 'testconfig',
backView: 'setup'
})}
>
<div className="text-black text-l font-bold">插座配置{currentConfig.params.wireOrder}</div>
<div className="w-3 h-3 relative">
<Image src="/arrow.png" alt="箭头" fill className="object-contain" />
</div>
</div>
</div>
);
case 'cfp':
return (
<div className="space-y-4">
<div className="space-y-0">
<div className="h-[50px] bg-gradient-to-b from-[#e6e3e6] to-[#7b797b] rounded-t-sm p-4 shadow-lg flex justify-between items-center">
<div className="text-black text-l font-bold">测试类型智能远端</div>
<div className="w-3 h-3 relative">
<Image src="/arrow.png" alt="箭头" fill className="object-contain" />
</div>
</div>
<div className="h-[50px] bg-gradient-to-b from-[#ffffff] to-[#ffffff] rounded-b-sm p-4 shadow-lg flex justify-between items-center">
<div className="text-black text-l">双向</div>
<div className="w-3 h-3 relative">
<Image src="/arrow.png" alt="箭头" fill className="object-contain" />
</div>
</div>
</div>
<div
className="h-[50px] bg-gradient-to-b from-[#e6e3e6] to-[#7b797b] rounded-sm p-4 shadow-lg flex justify-between items-center cursor-pointer"
onClick={() => navigateTo('menulist', 'setup', {
menuType: 'FIBER_TYPE',
selectedConfigId: selectedConfigId,
backTo: 'testconfig',
backView: 'setup'
})}
>
<div className="text-black text-l font-bold">光纤类型{currentConfig.params.cableType}</div>
<div className="w-3 h-3 relative">
<Image src="/arrow.png" alt="箭头" fill className="object-contain" />
</div>
</div>
<div className="space-y-0">
<div
className="h-[50px] bg-gradient-to-b from-[#e6e3e6] to-[#7b797b] rounded-t-sm p-4 shadow-lg flex justify-between items-center cursor-pointer"
onClick={() => navigateTo('menulist', 'setup', {
menuType: 'CFP_LIMIT',
selectedConfigId: selectedConfigId,
backTo: 'testconfig',
backView: 'setup'
})}
>
<div className="text-black text-l font-bold">测试极限值{currentConfig.params.limitValue}</div>
<div className="w-3 h-3 relative">
<Image src="/arrow.png" alt="箭头" fill className="object-contain" />
</div>
</div>
<div className="h-[50px] bg-gradient-to-b from-[#ffffff] to-[#ffffff] p-4 shadow-lg flex justify-between items-center">
<div className="text-black text-l ">参照方法{currentConfig.params.refJumper} 跳线</div>
<div className="w-3 h-3 relative">
<Image src="/arrow.png" alt="箭头" fill className="object-contain" />
</div>
</div>
<div className="h-[50px] bg-gradient-to-b from-[#ffffff] to-[#ffffff] p-4 shadow-lg flex justify-between items-center">
<div className="text-black text-l">连接器类型LC</div>
<div className="w-3 h-3 relative">
<Image src="/arrow.png" alt="箭头" fill className="object-contain" />
</div>
</div>
<div className="h-[50px] bg-gradient-to-b from-[#ffffff] to-[#ffffff] rounded-b-sm p-4 shadow-lg flex justify-between items-center cursor-pointer " onClick={() => updateCurrentView('cfp-conunt')}>
<div className="text-black text-l" >接线/接头的数量{currentConfig.params.connectorCount}/{currentConfig.params.spliceCount}</div>
<div className="w-3 h-3 relative">
<Image src="/arrow.png" alt="箭头" fill className="object-contain" />
</div>
</div>
</div>
</div>
);
case 'ofp':
return (
<div className="space-y-4">
<div className="space-y-0">
<div className="h-[50px] bg-gradient-to-b from-[#e6e3e6] to-[#7b797b] rounded-t-sm p-4 shadow-lg flex justify-between items-center">
<div className="text-black text-l font-bold">测试类型自动OTDR</div>
<div className="w-3 h-3 relative">
<Image src="/arrow.png" alt="箭头" fill className="object-contain" />
</div>
</div>
<div className="h-[50px] bg-gradient-to-b from-[#ffffff] to-[#ffffff] p-4 shadow-lg flex justify-between items-center">
<div className="text-black text-l ">前导补偿</div>
<div className="w-3 h-3 relative">
{/* <Image src="/arrow.png" alt="箭头" fill className="object-contain" /> */}
</div>
</div>
<div className="h-[50px] bg-gradient-to-b from-[#ffffff] to-[#ffffff] rounded-b-sm p-4 shadow-lg flex justify-between items-center">
<div className="text-black text-l ">波长{currentConfig.params.cableType.includes('OM') ? '850 nm、1310nm' : '1310nm、1550nm'}</div>
<div className="w-3 h-3 relative">
{/* <Image src="/arrow.png" alt="箭头" fill className="object-contain" /> */}
</div>
</div>
</div>
<div
className="h-[50px] bg-gradient-to-b from-[#e6e3e6] to-[#7b797b] rounded-sm p-4 shadow-lg flex justify-between items-center cursor-pointer"
onClick={() => navigateTo('menulist', 'setup', {
menuType: 'FIBER_TYPE',
selectedConfigId: selectedConfigId,
backTo: 'testconfig',
backView: 'setup'
})}
>
<div className="text-black text-l font-bold">光纤类型{currentConfig.params.cableType}</div>
<div className="w-3 h-3 relative">
<Image src="/arrow.png" alt="箭头" fill className="object-contain" />
</div>
</div>
<div
className="h-[50px] bg-gradient-to-b from-[#e6e3e6] to-[#7b797b] rounded-sm p-4 shadow-lg flex justify-between items-center cursor-pointer"
onClick={() => navigateTo('menulist', 'setup', {
menuType: 'OFP_LIMIT',
selectedConfigId: selectedConfigId,
backTo: 'testconfig',
backView: 'setup'
})}
>
<div className="text-black text-l font-bold">测试极限值{currentConfig.params.limitValue}</div>
<div className="w-3 h-3 relative">
<Image src="/arrow.png" alt="箭头" fill className="object-contain" />
</div>
</div>
</div>
);
default:
return null;
}
};
return (
<div className="flex-1 bg-[#303040] p-4">
<div className="h-full bg-gradient-to-b from-[#c6c3c6] to-[#c6c3c6] rounded-sm p-4 shadow-lg flex flex-col">
<div
className="flex justify-between items-center mb-4 cursor-pointer"
onClick={() => navigateTo('menulist', 'setup', {
menuType: 'MODULE',
selectedConfigId: selectedConfigId,
backTo: 'testconfig',
backView: 'setup'
})}
>
<div className="text-black font-bold text-lg">模块{currentConfig.modulelable}</div>
<div className="w-5 h-5 relative">
<Image src="/arrow.png" alt="箭头" fill className="object-contain" />
</div>
</div>
<div className="flex-1 overflow-auto">
{renderConfigContent()}
</div>
</div>
</div>
);
};
// 渲染标题栏
const renderTitleBar = () => {
const { view } = navigation.current;
const handleBack = () => {
if (view === 'setup') {
updateCurrentView('main');
} else {
goBack();
}
};
switch (view) {
case 'main':
return (
<TitleBar
title="更改测试"
backTo={navigation.previous?.name || 'home'}
view={navigation.previous?.view || 'main'}
params={navigation.previous?.params || {}}
onBack={handleBack}
/>
);
case 'setup':
return (
<TitleBar
title="测试设置"
backTo={navigation.previous?.name || 'testconfig'}
view={navigation.previous?.view || 'main'}
params={navigation.previous?.params || {}}
onBack={handleBack}
/>
);
case 'cfp-conunt':
return (
<TitleBar
title=" "
view={'setup'}
params={navigation.previous?.params || {}}
/>
);
}
};
// 渲染底部栏
const renderFooter = () => {
const { view } = navigation.current;
switch (view) {
case 'main':
return (
<div className="h-[60px] bg-[#303030] flex items-center justify-between px-8">
<button
onClick={() => {
// 创建新的测试配置
const newConfig = {
id: uuidv4(),
moduleType: '8000',
modulelable: 'DSX-8000',
params: {
limitValue: 'TIA Cat 6 Channel',
cableType: 'Cat6 U/UTP',
wireOrder: 'T568B',
refJumper: '1',
spliceCount: '0',
connectorCount: '2'
}
};
// 更新项目,添加新的测试配置
const updatedProject = {
...currentProject,
testConfigs: [...currentProject.testConfigs, newConfig]
};
updateProject(useDisplayStore.getState().selectedIndexes.projectIndex, updatedProject);
// 设置新配置为选中状态并保存到导航参数
setSelectedConfigId(newConfig.id);
updateCurrentView('setup', { selectedConfigId: newConfig.id });
}}
className="w-[100px] h-[40px] bg-gradient-to-b from-[#656565] to-[#313431] rounded-sm flex items-center justify-center text-white font-bold shadow-lg"
>
新测试
</button>
<button
onClick={() => {
if (!selectedConfigId) {
setToastMessage('请先选择一个测试配置');
return;
}
// 保存选中的配置ID到导航参数
updateCurrentView('setup', { selectedConfigId: selectedConfigId });
}}
className="w-[100px] h-[40px] bg-gradient-to-b from-[#656565] to-[#313431] rounded-sm flex items-center justify-center text-white font-bold shadow-lg"
>
编辑
</button>
<button
onClick={() => {
if (!selectedConfigId) {
setToastMessage('请先选择一个测试配置');
return;
}
const configIndex = currentProject.testConfigs.findIndex(
config => config.id === selectedConfigId
);
setSelectedIndexes({ testConfigIndex: configIndex });
navigateTo('home', 'main');
}}
className="w-[100px] h-[40px] bg-gradient-to-b from-[#ffd773] to-[#e7aa29] rounded-sm flex items-center justify-center text-black font-bold shadow-lg"
>
使用所选
</button>
</div>
);
case 'setup':
return (
<div className="h-[60px] bg-[#303030] flex items-center justify-end px-8">
<button
onClick={() => {
// 保存配置并返回主视图
updateCurrentView('main', { selectedConfigId: selectedConfigId });
}}
className="w-[100px] h-[40px] bg-gradient-to-b from-[#ffd773] to-[#e7aa29] rounded-sm flex items-center justify-center text-black font-bold shadow-lg"
>
保存
</button>
</div>
);
case 'cfp-conunt':
return (
<div className="h-[60px] bg-[#303030] flex items-center justify-end px-8">
<button
onClick={() => {
// 保存配置并返回主视图
updateCurrentView('setup', { selectedConfigId: selectedConfigId });
}}
className="w-[100px] h-[40px] bg-gradient-to-b from-[#ffd773] to-[#e7aa29] rounded-sm flex items-center justify-center text-black font-bold shadow-lg"
>
保存
</button>
</div>
);
default:
return null;
}
};
const renderCFPCONUNTContent = () =>{
const currentConfig = currentProject?.testConfigs.find(
config => config.id === selectedConfigId
);
if (!currentConfig) return null;
const handleValueChange = (field, value) => {
const currentConfig = currentProject?.testConfigs.find(
config => config.id === selectedConfigId
);
if (!currentConfig) return;
// 更新当前配置的参数
const updatedConfig = {
...currentConfig,
params: {
...currentConfig.params,
[field]: value
}
};
// 更新项目中的测试配置
const updatedProject = {
...currentProject,
testConfigs: currentProject.testConfigs.map(config =>
config.id === selectedConfigId ? updatedConfig : config
)
};
// 调用displayStore的updateProject方法更新状态
updateProject(useDisplayStore.getState().selectedIndexes.projectIndex, updatedProject);
};
return (
<div className="flex-1 bg-[#0f172a] flex flex-col relative">
<div className="absolute inset-0">
<Image
src={`/olts-weldingPointSet.png`}
alt="CFPCOUNUNT"
fill
className="object-contain"
sizes="100vw"
/>
</div>
{currentConfig.params.connectorCount > 2 && (<div>
<div className="absolute flex flex-col gap-10 pl-56.5 pt-45">
<div className="relative">
<Image
src={`/icon-connector.svg`}
alt="connector"
width={30}
height={30}
className="object-contain"
/>
<span className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-white">
{currentConfig.params.connectorCount-2}
</span>
</div>
</div>
<div className="absolute flex flex-col gap-10 pl-43 pt-45">
<div className="relative">
<Image
src={`/icon-connector.svg`}
alt="connector"
width={30}
height={30}
className="object-contain"
/>
<span className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-white">
{currentConfig.params.connectorCount-2}
</span>
</div>
</div>
</div>)}
{currentConfig.params.spliceCount > 0 && (<div>
<div className="absolute flex flex-col gap-10 pl-57 pt-65">
<div className="relative">
<div className="w-6 h-6 rounded-full bg-[#104878]"></div>
<span className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-white">
{currentConfig.params.spliceCount}
</span>
</div>
</div>
<div className="absolute flex flex-col gap-10 pl-44 pt-65">
<div className="relative">
<div className="w-6 h-6 rounded-full bg-[#104878]"></div>
<span className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-white">
{currentConfig.params.spliceCount}
</span>
</div>
</div>
</div>)}
<div className="relative flex flex-col gap-10 pl-75 pt-30">
<div className="flex items-center">
<input
type="number"
min="2"
max="20"
defaultValue={currentConfig.params.connectorCount}
onChange={(e) => handleValueChange('connectorCount', e.target.value)}
onKeyDown={(e) => e.preventDefault()}
className="w-16 h-8 px-2 bg-[#104878] rounded text-center focus:outline-none focus:ring-2 focus:ring-[#0ff] text-white"
/>
<span className="ml-4 text-base font-roboto select-none text-black">全部连接</span>
</div>
<div className="flex items-center">
<input
type="number"
min="0"
max="18"
defaultValue={currentConfig.params.spliceCount}
onChange={(e) => handleValueChange('spliceCount', e.target.value)}
onKeyDown={(e) => e.preventDefault()}
className="w-16 h-8 px-2 bg-[#104878] rounded text-center focus:outline-none focus:ring-2 focus:ring-[#0ff] text-white"
/>
<span className="ml-4 text-base font-roboto select-none text-black">接头</span>
</div>
<div className="flex items-center">
<input
type="number"
min="1"
max="1"
defaultValue={currentConfig.params.refJumper}
onChange={(e) => handleValueChange('refJumper', e.target.value)}
onKeyDown={(e) => e.preventDefault()}
className="w-16 h-8 px-2 bg-[#104878] rounded text-center focus:outline-none focus:ring-2 focus:ring-[#0ff] text-white"
/>
<span className="ml-4 text-base font-roboto select-none text-black">跳线参照</span>
</div>
</div>
</div>
);
};
// 渲染内容
const renderContent = () => {
const { view } = navigation.current;
switch (view) {
case 'main':
return renderMainContent();
case 'setup':
return renderSetupContent();
case 'cfp-conunt':
return renderCFPCONUNTContent();
default:
return renderMainContent();
}
};
return (
<div className="w-full h-full flex flex-col overflow-hidden">
<StatusBar />
{renderTitleBar()}
{renderContent()}
{renderFooter()}
</div>
);
}

View File

@@ -0,0 +1,747 @@
import React, { useState, useEffect } from 'react';
import StatusBar from '../lib/StatusBar';
import TitleBar from '../lib/TitleBar';
import useDisplayStore from '@/store/displayStore';
import useDeviceStore from '@/store/deviceStore';
import { v4 as uuidv4 } from 'uuid';
// 计算电阻余量的函数
function calculateResistanceMargin(data, limitdata, limitValue) {
try {
// 判断测试类型
const isPoETest = limitValue.includes('+PoE');
const isALLTest = limitValue.includes('+ALL');
let minMargin = Infinity;
// 回路电阻计算
const loopResistanceData = data?.performance?.data?.['Loop Resistance (Ω)'];
const loopResistanceLimit = limitdata?.['Loop Resistance (Ω)']?.['PAIRLimit (Ω)'];
if (loopResistanceData && loopResistanceLimit) {
const pairs = ['PAIR12', 'PAIR36', 'PAIR45', 'PAIR78'];
for (const pair of pairs) {
const actualValues = loopResistanceData[pair];
if (actualValues && Array.isArray(actualValues)) {
for (let i = 0; i < actualValues.length && i < loopResistanceLimit.length; i++) {
const actualValue = actualValues[i];
const limitVal = loopResistanceLimit[i];
if (actualValue !== undefined && actualValue !== null && limitVal !== undefined && limitVal !== null) {
const margin = limitVal - Math.abs(actualValue);
minMargin = Math.min(minMargin, margin);
}
}
}
}
}
// 线对UBL计算仅在+PoE或+ALL测试时
if (isPoETest || isALLTest) {
const ublData = data?.performance?.data?.['UBL (Ω)'];
const ublLimit = limitdata?.['UBL (Ω)']?.['PAIRLimit (Ω)'];
if (ublData && ublLimit) {
const pairs = ['PAIR12', 'PAIR36', 'PAIR45', 'PAIR78'];
for (const pair of pairs) {
const actualValues = ublData[pair];
if (actualValues && Array.isArray(actualValues)) {
for (let i = 0; i < actualValues.length && i < ublLimit.length; i++) {
const actualValue = actualValues[i];
const limitVal = ublLimit[i];
if (actualValue !== undefined && actualValue !== null && limitVal !== undefined && limitVal !== null) {
const margin = limitVal - Math.abs(actualValue);
minMargin = Math.min(minMargin, margin);
}
}
}
}
}
}
// P2PUBL计算仅在+PoE或+ALL测试时
if (isPoETest || isALLTest) {
const p2pUblData = data?.performance?.data?.['P2PUBL (Ω)'];
const p2pUblLimit = limitdata?.['P2PUBL (Ω)']?.['PAIRLimit (Ω)'];
if (p2pUblData && p2pUblLimit) {
const pairs = ['PAIR1236', 'PAIR1245', 'PAIR1278', 'PAIR3645', 'PAIR3678', 'PAIR4578'];
for (const pair of pairs) {
const actualValues = p2pUblData[pair];
if (actualValues && Array.isArray(actualValues)) {
for (let i = 0; i < actualValues.length && i < p2pUblLimit.length; i++) {
const actualValue = actualValues[i];
const limitVal = p2pUblLimit[i];
if (actualValue !== undefined && actualValue !== null && limitVal !== undefined && limitVal !== null) {
const margin = limitVal - Math.abs(actualValue);
minMargin = Math.min(minMargin, margin);
}
}
}
}
}
}
return minMargin === Infinity ? 0 : minMargin;
} catch (error) {
console.error('Error calculating resistance margin:', error);
return 0;
}
}
export default function Testing() {
const { view } = useDisplayStore.getState().navigation.current;
const { navigateTo,
getCurrentTestConfig,
getCurrentProject,
getCurrentOperator,
getCurrentCableId,
getCurrentCableId2,
setToastMessage,
} = useDisplayStore();
const currentConfig = getCurrentTestConfig();
const { connectionStatus,setTotalToastMessage } = useDeviceStore();
// 铜缆测试路径和状态
const copperMainPathKey = connectionStatus?.mainPaths ? Object.keys(connectionStatus.mainPaths)[0] : null;
const copperRemotePathKey = connectionStatus?.remotePaths ? Object.keys(connectionStatus.remotePaths)[0] : null;
const coppermainend = copperMainPathKey ? connectionStatus.mainPaths[copperMainPathKey]?.end : null;
const copperremoteend = copperRemotePathKey ? connectionStatus.remotePaths[copperRemotePathKey]?.end : null;
const copperRefStatus = useDisplayStore.getState()?.ref?.copper?.status || null;
// CFP测试路径和状态
const cableType = currentConfig.params.cableType;
const isMultiMode = cableType.includes('OM');
const cfpRefStatus = useDisplayStore.getState()?.ref?.cfp?.status || null;
const cfpRefConnectStatus = useDisplayStore.getState()?.ref?.cfp?.connectStatus || null;
const cfpInResult = isMultiMode ? connectionStatus?.CFPMainIn : connectionStatus?.CFPMainOut;
const cfpOutResult = isMultiMode ? connectionStatus?.CFPMainOut : connectionStatus?.CFPMainIn;
// 获取isMultiMode
const wavelength1 = isMultiMode ? '850' : '1310';
const wavelength2 = isMultiMode ? '1300' : '1550';
// OFP测试路径和状态
const ofpMainEnd = copperMainPathKey ? connectionStatus.mainPaths[copperMainPathKey]?.end : null;
const ofpRefStatus = useDisplayStore.getState()?.ref?.ofp?.status || null;
const ofpRefConnectStatus = useDisplayStore.getState()?.ref?.ofp?.connectStatus || null;
const [tempTestResult, setTempTestResult] = useState(null);
// 获取测试链路清洁状态
const moduleType = currentConfig?.moduleType;
// 检查路径中所有端口的清洁状态
const checkPortsCleanStatus = (paths) => {
if (!paths || paths.length === 0) return false;
// 检查每个端口的清洁状态
const cleanPorts = paths.filter(portId => {
const portElement = document.getElementById(portId);
return portElement?.getAttribute('lcclean') === 'true';
});
// 返回已清洁的端口数量
return cleanPorts.length;
};
let allPathsCleanStatus = {};
let isAllPathsClean = 0; // 默认为0表示全部未清洁
// 只在模块类型为 cfp 或 ofp 时执行检测
if (moduleType === 'cfp' || moduleType === 'ofp') {
if (moduleType === 'cfp') {
// CFP模块检测所有路径
const mainInPaths = connectionStatus?.mainPaths?.['main-cfp-in']?.path || [];
const mainOutPaths = connectionStatus?.mainPaths?.[isMultiMode ? 'main-cfp-mm-out' : 'main-cfp-sm-out']?.path || [];
const remoteInPaths = connectionStatus?.remotePaths?.['remote-cfp-in']?.path || [];
const remoteOutPaths = connectionStatus?.remotePaths?.[isMultiMode ? 'remote-cfp-mm-out' : 'remote-cfp-sm-out']?.path || [];
// 获取每个路径的清洁端口数量
const mainInClean = checkPortsCleanStatus(mainInPaths);
const mainOutClean = checkPortsCleanStatus(mainOutPaths);
const remoteInClean = checkPortsCleanStatus(remoteInPaths);
const remoteOutClean = checkPortsCleanStatus(remoteOutPaths);
// 计算总端口数和已清洁端口数
const totalPorts = mainInPaths.length + mainOutPaths.length +
remoteInPaths.length + remoteOutPaths.length;
const cleanedPorts = mainInClean + mainOutClean +
remoteInClean + remoteOutClean;
// 设置清洁状态
if (cleanedPorts === 0) isAllPathsClean = 0;
else if (cleanedPorts < totalPorts) isAllPathsClean = 1;
else if (cleanedPorts === totalPorts) isAllPathsClean = 2;
allPathsCleanStatus = {
mainInClean: mainInClean === mainInPaths.length,
mainOutClean: mainOutClean === mainOutPaths.length,
remoteInClean: remoteInClean === remoteInPaths.length,
remoteOutClean: remoteOutClean === remoteOutPaths.length
};
} else {
// OFP模块只检测主机输出端口
const mainOutPaths = connectionStatus?.mainPaths?.[isMultiMode ? 'main-ofp-mm-out' : 'main-ofp-sm-out']?.path || [];
const cleanedPorts = checkPortsCleanStatus(mainOutPaths);
// 设置清洁状态
if (cleanedPorts === 0) isAllPathsClean = 0;
else if (cleanedPorts < mainOutPaths.length) isAllPathsClean = 1;
else if (cleanedPorts === mainOutPaths.length) isAllPathsClean = 2;
allPathsCleanStatus = {
mainOutClean: cleanedPorts === mainOutPaths.length
};
}
}
useEffect(() => {
if (!tempTestResult) {
const loadTestResult = async () => {
try {
const currentProject = getCurrentProject();
const currentOperator = getCurrentOperator();
// 获取当前选中的电缆ID
const currentCableId = getCurrentCableId().name;
// 获取当前选中的电缆ID2
const currentCableId2 = getCurrentCableId2().name;
const CopperWiremapStatus = connectionStatus?.CopperWiremapStatus;
const CopperPerformanceStatus = connectionStatus?.CopperPerformanceStatus;
const ofpResult = connectionStatus?.OFPStatus;
const moduleType = currentConfig?.moduleType;
const cableType = currentConfig?.params?.cableType;
const wireOrder = currentConfig?.params?.wireOrder;
let testResult;
// 不同模块组装测试结果逻辑
if (moduleType === '8000') {
let CopperResultStatus = 'fail';
let CopperWiremapResultStatus = 'fail';
if (CopperWiremapStatus === 'pass' ) {
if (wireOrder === 'T568B' ||
wireOrder === 'Ethernet Two-Pair' ||
wireOrder === 'M12-D Two-Pair') {
CopperWiremapResultStatus = 'pass';
console.log(CopperWiremapResultStatus);
}
} else if (CopperWiremapStatus === 'pass-2pair') {
if (wireOrder === 'Ethernet Two-Pair' ||
wireOrder === 'M12-D Two-Pair') {
CopperWiremapResultStatus = 'pass';
} else if (wireOrder === 'T568B') {
CopperWiremapResultStatus = 'fail';
CopperResultStatus = 'fail';
}
}else if (CopperWiremapStatus === 'sopen') {
if (wireOrder === 'T568B') {
CopperWiremapResultStatus = 'fail';
CopperResultStatus = 'fail';
}
else if (cableType.includes('U/UTP')) {
CopperWiremapResultStatus = 'pass';
}
else if (cableType.includes('F/UTP')) {
CopperWiremapResultStatus = 'fail';
CopperResultStatus = 'fail';
}
}
if (CopperWiremapResultStatus === 'pass') {
try {
const response = await import(`@/store/COPPER/${CopperPerformanceStatus}.json`);
const data = response.default;
// 加载限制数据
const limitValue = currentConfig?.params?.limitValue;
try {
// 从limitValue中提取基础名称(移除+PoE和+ALL后缀)
const baseName = limitValue.split(' (+')[0];
// 使用基础名称加载带有(+ALL)后缀的文件
const limitResponse = await import(`@/store/COPPER/${baseName} (+ALL).json`);
const limitdata = limitResponse;
// 计算所有参数的余量
const paramTitles = ['插入损耗', '回波损耗', 'NEXT', 'PS NEXT', 'ACR-N', 'PS ACR-N',
'ACR-F', 'PS ACR-F', 'CDNEXT', 'CMRL', 'TCL', 'ELTCTL', '电阻'];
const dataGroupMap = {
'插入损耗': 'Insertion Loss (dB)',
'回波损耗': 'RL (dB)',
'NEXT': 'NEXT (dB)',
'PS NEXT': 'PS NEXT (dB)',
'ACR-N': 'ACR-N (dB)',
'PS ACR-N': 'PS ACR-N (dB)',
'ACR-F': 'ACR-F (dB)',
'PS ACR-F': 'PS ACR-F (dB)',
'CDNEXT': 'CDNEXT (dB)',
'CMRL': 'CMRL (dB)',
'TCL': 'TCL (dB)',
'ELTCTL': 'ELTCTL (dB)',
'电阻': 'Resistance (Ω)'
};
let hasNegativeMargin = false;
// 长度判断
if (limitdata.LENGTH && data.performance.LENGTH) {
const lengthPairs = ['PAIR12', 'PAIR36', 'PAIR45', 'PAIR78'];
for (const pair of lengthPairs) {
if (data.performance.LENGTH[pair] > limitdata.LENGTH) {
hasNegativeMargin = true;
break;
}
}
}
// 电阻判断
if (limitdata.LOOP && data.performance.OHM?.LOOP) {
const loopPairs = ['PAIR12', 'PAIR36', 'PAIR45', 'PAIR78'];
for (const pair of loopPairs) {
if (data.performance.OHM.LOOP[pair] > limitdata.LOOP) {
hasNegativeMargin = true;
console.log("电阻fail")
break;
}
}
}
// 检查是否是 +PoE 或 +ALL 测试
const hasPoE = limitValue?.includes('+PoE');
const hasALL = limitValue?.includes('+ALL');
// +PoE 或 +ALL 测试的额外检查
if ((hasPoE || hasALL) && data.performance.OHM) {
// 线对 UBL 余量检查
if (limitdata.PAIRUBL && data.performance.OHM.PAIRUBL) {
const pairUblPairs = ['PAIR12', 'PAIR36', 'PAIR45', 'PAIR78'];
for (const pair of pairUblPairs) {
if (Math.abs(data.performance.OHM.PAIRUBL[pair]) > limitdata.PAIRUBL) {
hasNegativeMargin = true;
console.log("UBL fail")
break;
}
}
}
// P2P UBL 余量检查
if (limitdata.P2PUBL && data.performance.OHM.P2PUBL) {
const p2pUblPairs = ['PAIR1236', 'PAIR1245', 'PAIR1278', 'PAIR3645', 'PAIR3678', 'PAIR4578'];
for (const pair of p2pUblPairs) {
if (Math.abs(data.performance.OHM.P2PUBL[pair]) > limitdata.P2PUBL) {
hasNegativeMargin = true;
console.log("P2P fail")
break;
}
}
}
}
for (const paramTitle of paramTitles) {
const dataGroup = dataGroupMap[paramTitle];
// 电阻参数的特殊处理
if (paramTitle === '电阻') {
const resistanceMargin = calculateResistanceMargin(data, limitdata, limitValue);
if (resistanceMargin < 0) {
hasNegativeMargin = true;
}
continue;
}
if (!dataGroup || !data?.performance?.data?.[dataGroup] || !limitdata?.[dataGroup]) continue;
const testData = data.performance.data[dataGroup];
const limitValues = limitdata[dataGroup]?.['PAIRLimit (dB)'] || [];
const pairs = ['NEXT (dB)', 'ACR-N (dB)'].includes(dataGroup) ?
['PAIR1236', 'PAIR1245', 'PAIR1278', 'PAIR3645', 'PAIR3678', 'PAIR4578'] :
['ACR-F (dB)', 'CDNEXT (dB)'].includes(dataGroup) ?
['PAIR1236', 'PAIR1245', 'PAIR1278', 'PAIR3612', 'PAIR3645', 'PAIR3678',
'PAIR4512', 'PAIR4535', 'PAIR4578', 'PAIR7812', 'PAIR7936', 'PAIR7845'] :
['PAIR12', 'PAIR36', 'PAIR45', 'PAIR78'];
for (let index = 0; index < limitValues.length && !hasNegativeMargin; index++) {
const limitValue = limitValues[index];
if (limitValue === undefined || limitValue === null) break;
for (const pair of pairs) {
const actualValue = testData[pair]?.[index];
if (actualValue !== undefined && actualValue !== null) {
const margin = paramTitle === '插入损耗' ?
Math.abs(limitValue) - Math.abs(actualValue) :
Math.abs(actualValue) - limitValue;
if (margin < 0) {
hasNegativeMargin = true;
break;
}
}
}
}
}
CopperResultStatus = hasNegativeMargin ? 'fail' : 'pass';
} catch (error) {
console.error('Error calculating margins:', error);
}
} catch (error) {
console.error('加载测试结果失败:', error);
}
}
testResult = {
id: uuidv4(),
name: currentProject?.cableIds[0]?.name || '',
testconfig: JSON.parse(JSON.stringify(currentConfig || {})),
operators: currentOperator?.name || '',
CopperWiremapStatus,
CopperWiremapResultStatus,
CopperPerformanceStatus,
CopperRef: copperRefStatus,
CopperResultStatus,
};
} else if (moduleType === 'cfp' ) {
// 极限值导入
const limitValue = currentConfig?.params?.limitValue;
const CFP_LIMIT = require('@/store/FIBER/CFP_LIMIT').default;
const limitData = CFP_LIMIT[limitValue];
if (limitData) {
// 使用limitData中的极限值进行后续处理
const {
adapterLoss,
firstLastAdapterLoss,
spliceLoss,
totalLoss1310nm,
totalLoss1550nm,
lossPerKm850nm,
lossPerKm13000nm,
lossPerKm1310nm,
lossPerKm1550nm,
maxSMLength,
manMMLength
} = limitData;
}
// 用户配置加载
const connectorCount = currentConfig?.params?.connectorCount;
const spliceCount = currentConfig?.params?.spliceCount;
const refJumper = currentConfig?.params?.refJumper;
let cfpMainInStatus = null;
let cfpMainOutStatus = null;
if (cfpInResult) {
try {
// 加载第一个波长的数据
const wave1Data = await import(`@/store/FIBER/${cfpInResult}/${wavelength1}-dump.json`);
// 加载第二个波长的数据
const wave2Data = await import(`@/store/FIBER/${cfpInResult}/${wavelength2}-dump.json`);
// 添加随机浮动因子损耗上下浮动1%
const getDistanceRandomFactor = () => 1; // 长度不浮动
const getLossRandomFactor = () => 1 + (Math.random() * 0.02 - 0.01); // 生成0.99到1.01之间的随机数
// 先计算两个波长的损耗值
const wave1Loss = wave1Data.Summary.totalLoss * getLossRandomFactor();
const wave2Loss = wave2Data.Summary.totalLoss * getLossRandomFactor();
// 如果是参考状态增加0.1
const finalWave1Loss = !cfpRefStatus ? wave1Loss : wave1Loss + 0.1;
const finalWave2Loss = !cfpRefStatus ? wave2Loss : wave2Loss + 0.1;
cfpMainInStatus = {
wavelength1: {
wave: wavelength1,
distance: (wave1Data.Summary.totalDistance * getDistanceRandomFactor()).toFixed(2),
loss: finalWave1Loss.toFixed(2)
},
wavelength2: {
wave: wavelength2,
distance: (wave2Data.Summary.totalDistance * getDistanceRandomFactor()).toFixed(2),
loss: finalWave2Loss.toFixed(2)
}
};
} catch (error) {
setTimeout(() => {
setTotalToastMessage("被测链路与参考跳纤类型不一致,请检查");
}, 1500); // 3000 毫秒 = 3 秒
return;
}
}
if (cfpOutResult) {
try {
// 加载第一个波长的数据
const wave1Data = await import(`@/store/FIBER/${cfpOutResult}/${wavelength1}-dump.json`);
// 加载第二个波长的数据
const wave2Data = await import(`@/store/FIBER/${cfpOutResult}/${wavelength2}-dump.json`);
// 添加随机浮动因子损耗上下浮动1%
const getDistanceRandomFactor = () => 1; // 长度不浮动
const getLossRandomFactor = () => 1 + (Math.random() * 0.02 - 0.01); // 生成0.99到1.01之间的随机数
// 先计算两个波长的损耗值
const wave1Loss = wave1Data.Summary.totalLoss * getLossRandomFactor();
const wave2Loss = wave2Data.Summary.totalLoss * getLossRandomFactor();
// 如果是参考状态增加0.1
const finalWave1Loss = !cfpRefStatus ? wave1Loss : wave1Loss + 0.1;
const finalWave2Loss = !cfpRefStatus ? wave2Loss : wave2Loss + 0.1;
cfpMainOutStatus = {
wavelength1: {
wave: wavelength1,
distance: (wave1Data.Summary.totalDistance * getDistanceRandomFactor()).toFixed(2),
loss: finalWave1Loss.toFixed(2)
},
wavelength2: {
wave: wavelength2,
distance: (wave2Data.Summary.totalDistance * getDistanceRandomFactor()).toFixed(2),
loss: finalWave2Loss.toFixed(2)
}
};
} catch (error) {
setTimeout(() => {
setToastMessage("未检测到光纤,请检查接口");
}, 1500); // 3000 毫秒 = 3 秒
return;
}
}
// 结果计算与判断
// 第一根光纤
const cfpMainIndistance = cfpMainInStatus?.wavelength1?.distance || 0;
const cfpMainIndistance2 = cfpMainInStatus?.wavelength2?.distance || 0;
const cfpMainInloss = cfpMainInStatus?.wavelength1?.loss || 0;
const cfpMainInloss2 = cfpMainInStatus?.wavelength2?.loss || 0;
// 长度判断
const maxLength = isMultiMode ? limitData.maxMMLength : limitData.maxSMLength;
const isLengthValidIn = cfpMainIndistance <= maxLength &&
cfpMainIndistance2 <= maxLength ;
// 损耗判断
const lossPerKm = isMultiMode ? (limitData.lossPerKm850nm):(limitData.lossPerKm1310nm);
const lossPerKm2 = isMultiMode ? (limitData.lossPerKm1300nm):(limitData.lossPerKm1550nm);
const totalLossLimit = limitData.adapterLoss !== null ?
connectorCount * limitData.adapterLoss +
spliceCount * limitData.spliceLoss +
(cfpMainIndistance/1000) * lossPerKm
: isMultiMode ?
(limitData.totalLoss850nm):(limitData.totalLoss1310nm);
const totalLossLimit2 = limitData.adapterLoss !== null ?
connectorCount * limitData.adapterLoss +
spliceCount * limitData.spliceLoss +
(cfpMainIndistance2/1000) * lossPerKm2
: isMultiMode ?
(limitData.totalLoss1300nm):(limitData.totalLoss1550nm);
const isLossValidIn = cfpMainInloss <= totalLossLimit &&
cfpMainInloss2 <= totalLossLimit2 ;
// 第二根光纤
const cfpMainOutdistance = cfpMainOutStatus?.wavelength1?.distance || 0;
const cfpMainOutdistance2 = cfpMainOutStatus?.wavelength2?.distance || 0;
const cfpMainOutloss = cfpMainOutStatus?.wavelength1?.loss || 0;
const cfpMainOutloss2 = cfpMainOutStatus?.wavelength2?.loss || 0;
// 长度判断
const maxLengthOut = isMultiMode ? limitData.maxMMLength : limitData.maxSMLength;
const isLengthValidOut = cfpMainOutdistance <= maxLength &&
cfpMainOutdistance2 <= maxLength ;
// 损耗判断
const lossPerKmOut = isMultiMode ? (limitData.lossPerKm850nm):(limitData.lossPerKm1310nm);
const lossPerKm2Out = isMultiMode ? (limitData.lossPerKm1300nm):(limitData.lossPerKm1550nm);
const totalLossLimitOut = limitData.adapterLoss !== null ?
connectorCount * limitData.adapterLoss +
spliceCount * limitData.spliceLoss +
(cfpMainIndistance/1000) * lossPerKmOut
: isMultiMode ?
(limitData.totalLoss850nm):(limitData.totalLoss1310nm);
const totalLossLimit2Out = limitData.adapterLoss !== null ?
connectorCount * limitData.adapterLoss +
spliceCount * limitData.spliceLoss +
(cfpMainOutdistance2/1000) * lossPerKm2Out
: isMultiMode ?
(limitData.totalLoss1300nm):(limitData.totalLoss1550nm);
const isLossValidOut = cfpMainOutloss <= totalLossLimitOut &&
cfpMainOutloss2 <= totalLossLimit2Out ;
// 结果判断
const CFPResultStatus = isLengthValidIn && isLossValidIn && isLengthValidOut && isLossValidOut ? 'pass' : 'fail';
// IN端口通过失败判断根据 isMultiMode 判断使用哪组参数)
const InPortStatus = isMultiMode
? (isLengthValidIn && isLossValidIn ? 'pass' : 'fail')
: (isLengthValidOut && isLossValidOut ? 'pass' : 'fail');
// OUT端口通过失败判断
const OutPortStatus = isMultiMode
? (isLengthValidOut && isLengthValidOut ? 'pass' : 'fail')
: (isLengthValidIn && isLossValidIn ? 'pass' : 'fail');
testResult = {
id: uuidv4(),
name: currentCableId || '',
name2: currentCableId2 || '',
testconfig: JSON.parse(JSON.stringify(currentConfig || {})),
operators: currentOperator?.name || '',
CFPRef:cfpRefStatus,
CFPRefConnect:cfpRefConnectStatus,
CFPMainIn:cfpMainInStatus,
isLengthValidIn,
isLossValidIn,
InPortStatus, // 添加IN端口状态
isLengthValidOut,
isLossValidOut,
OutPortStatus, // 添加OUT端口状态
CFPMainOut:cfpMainOutStatus,
CFPResultStatus,
PortCleanStatus:isAllPathsClean,
};
} else if (moduleType === 'ofp') {
const ofpConnectionStatus = connectionStatus;
let ofpResultStatus = 'fail';
if (ofpResult.includes('pass')) {
if (!ofpRefStatus) {
if (connectionStatus.OFPoutRefStatus.includes('sm-') || connectionStatus.OFPoutRefStatus.includes('mm-')) {
ofpResultStatus = 'fail';
} else {
ofpResultStatus = 'pass';
}
} else if (ofpRefStatus === 'start') {
if (connectionStatus.OFPoutRefStatus.includes('smc-') || connectionStatus.OFPoutRefStatus.includes('mmc-')) {
ofpResultStatus = 'pass';
}
} else if (ofpRefStatus === 'end') {
const hasSmOrMm = connectionStatus.OFPoutRefStatus.includes('sm-') || connectionStatus.OFPoutRefStatus.includes('mm-');
const hasSmcOrMmc = connectionStatus.OFPoutRefStatus.includes('smc-') || connectionStatus.OFPoutRefStatus.includes('mmc-');
const hasConnectedSmOrMm = connectionStatus.OFPConnectedToRefStatus?.includes('sm-') || connectionStatus.OFPConnectedToRefStatus?.includes('mm-');
const hasConnectedSmcOrMmc = connectionStatus.OFPConnectedToRefStatus?.includes('smc-') || connectionStatus.OFPConnectedToRefStatus?.includes('mmc-');
// 只有前导和末尾都是补偿线时才是pass其他情况都是fail
if (hasSmcOrMmc && hasConnectedSmcOrMmc) {
ofpResultStatus = 'pass';
} else {
ofpResultStatus = 'fail';
}
}
} else if (ofpResult === 'connector-fail-end') {
if (!ofpRefStatus) {
if (connectionStatus.OFPoutRefStatus.includes('sm-') || connectionStatus.OFPoutRefStatus.includes('mm-')) {
ofpResultStatus = 'fail';
} else {
ofpResultStatus = 'pass';
}
} else if (ofpRefStatus === 'start') {
if (connectionStatus.OFPoutRefStatus.includes('smc-') || connectionStatus.OFPoutRefStatus.includes('mmc-')) {
ofpResultStatus = 'pass';
}
} else if (ofpRefStatus === 'end') {
const hasSmOrMm = connectionStatus.OFPoutRefStatus.includes('sm-') || connectionStatus.OFPoutRefStatus.includes('mm-');
const hasSmcOrMmc = connectionStatus.OFPoutRefStatus.includes('smc-') || connectionStatus.OFPoutRefStatus.includes('mmc-');
const hasConnectedSmOrMm = connectionStatus.OFPConnectedToRefStatus?.includes('sm-') || connectionStatus.OFPConnectedToRefStatus?.includes('mm-');
const hasConnectedSmcOrMmc = connectionStatus.OFPConnectedToRefStatus?.includes('smc-') || connectionStatus.OFPConnectedToRefStatus?.includes('mmc-');
// 只有前导和末尾都是补偿线时才是pass其他情况都是fail
if (hasSmcOrMmc && hasConnectedSmcOrMmc) {
ofpResultStatus = 'pass';
} else {
ofpResultStatus = 'fail';
}
}
}else if (ofpResult === 'connector-fail-start') {
if (ofpRefStatus === 'start') {
const hasSmOrMm = connectionStatus.OFPoutRefStatus.includes('sm-') || connectionStatus.OFPoutRefStatus.includes('mm-');
const hasSmcOrMmc = connectionStatus.OFPoutRefStatus.includes('smc-') || connectionStatus.OFPoutRefStatus.includes('mmc-');
if(hasSmOrMm){
ofpResultStatus = 'fail';
}else if(hasSmcOrMmc){
ofpResultStatus = 'fail';
}
}
}
testResult = {
id: uuidv4(),
name: currentProject?.cableIds[0]?.name || '',
testconfig: JSON.parse(JSON.stringify(currentConfig || {})),
operators: currentOperator?.name || '',
ofpResult,
ofpRefStatus,
OFPRefConnect:ofpRefConnectStatus,
ofpConnectionStatus,
ofpResultStatus,
PortCleanStatus:isAllPathsClean,
};
}
setTempTestResult(testResult);
} catch (error) {
console.error('加载测试结果失败:', error);
}
};
loadTestResult();
}
}, [connectionStatus?.CopperWiremapStatus, connectionStatus?.CopperPerformanceStatus]);
useEffect(() => {
if (tempTestResult) {
const moduleType = tempTestResult.testconfig?.moduleType;
let delayTime = 1500; // 默认延迟1.5秒
if (moduleType === '8000') {
delayTime = 5000; // 8000模块延迟5秒
} else if (moduleType === 'cfp') {
delayTime = 1500; // cfp模块延迟1.5秒
} else if (moduleType === 'ofp') {
delayTime = 6000; // ofp模块延迟6秒
}
const timer = setTimeout(() => {
navigateTo('resultinfo', 'nosave', tempTestResult);
}, delayTime);
return () => clearTimeout(timer);
}
}, [tempTestResult]);
return (
<div className="w-full h-full flex flex-col overflow-hidden">
<StatusBar />
<TitleBar
title="进程"
/>
<div className="h-[490px] bg-[#303040] relative">
{/* 背景图片 */}
<div
className="absolute inset-0 bg-cover bg-center bg-no-repeat"
style={{ backgroundImage: 'url(/testing.gif)' }}
/>
{/* 测试配置limit值 */}
<div className="absolute w-full top-8 z-10">
<span className="flex items-center justify-center text-black text-xl font-bold">
{currentConfig?.params?.limitValue || '未设置'}
</span>
</div>
</div>
<div className="h-[60px] bg-[#303030] flex items-center justify-center px-4">
<button
className="w-[100px] h-[40px] bg-gradient-to-b from-[#656565] to-[#313431] rounded-sm flex items-center justify-center text-white font-bold shadow-lg"
onClick={() => navigateTo('home', 'main')}
>
取消
</button>
</div>
</div>
);
}

File diff suppressed because it is too large Load Diff