新增关阀搜索面板;升级项目依赖;建立测试框架

This commit is contained in:
JIANG
2026-01-30 18:22:41 +08:00
parent 133e812700
commit 799eab03d0
8 changed files with 4020 additions and 247 deletions

30
jest.config.js Normal file
View File

@@ -0,0 +1,30 @@
const nextJest = require('next/jest')
const createJestConfig = nextJest({
// Provide the path to your Next.js app to load next.config.js and .env files in your test environment
dir: './',
})
// Add any custom config to be passed to Jest
const customJestConfig = {
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
testEnvironment: 'jest-environment-jsdom',
moduleNameMapper: {
'^@pages/(.*)$': '<rootDir>/pages/$1',
'^@/(.*)$': '<rootDir>/src/$1',
'^@app/(.*)$': '<rootDir>/src/app/$1',
'^@assets/(.*)$': '<rootDir>/src/assets/$1',
'^@components/(.*)$': '<rootDir>/src/components/$1',
'^@config/(.*)$': '<rootDir>/src/config/$1',
'^@contexts/(.*)$': '<rootDir>/src/contexts/$1',
'^@interfaces/(.*)$': '<rootDir>/src/interfaces/$1',
'^@libs/(.*)$': '<rootDir>/src/libs/$1',
'^@providers/(.*)$': '<rootDir>/src/providers/$1',
'^@utils/(.*)$': '<rootDir>/src/utils/$1',
// Handle specific aliases if generic one causes issues, but trying generic first matching tsconfig
// '^@(.*)$': '<rootDir>/src/$1',
},
}
// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
module.exports = createJestConfig(customJestConfig)

1
jest.setup.js Normal file
View File

@@ -0,0 +1 @@
import '@testing-library/jest-dom'

4042
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,6 +10,8 @@
"build": "refine build",
"start": "refine start",
"lint": "next lint",
"test": "jest",
"test:watch": "jest --watch",
"refine": "refine"
},
"dependencies": {
@@ -22,12 +24,12 @@
"@mui/x-data-grid": "^7.22.2",
"@mui/x-date-pickers": "^8.12.0",
"@refinedev/cli": "^2.16.50",
"@refinedev/core": "^5.0.6",
"@refinedev/core": "^5.0.8",
"@refinedev/devtools": "^2.0.3",
"@refinedev/kbar": "^2.0.1",
"@refinedev/mui": "^7.0.1",
"@refinedev/mui": "^8.0.0",
"@refinedev/nextjs-router": "^7.0.4",
"@refinedev/react-hook-form": "^5.0.3",
"@refinedev/react-hook-form": "^5.0.4",
"@refinedev/simple-rest": "^6.0.1",
"@tailwindcss/postcss": "^4.1.13",
"@turf/turf": "^7.2.0",
@@ -37,7 +39,7 @@
"echarts": "^6.0.0",
"echarts-for-react": "^3.0.5",
"js-cookie": "^3.0.5",
"next": "15.5.9",
"next": "^15.5.11",
"next-auth": "^4.24.5",
"ol": "^10.7.0",
"postcss": "^8.5.6",
@@ -50,14 +52,20 @@
},
"devDependencies": {
"@svgr/webpack": "^8.1.0",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
"@types/jest": "^30.0.0",
"@types/js-cookie": "^3.0.6",
"@types/node": "^20",
"@types/react": "^19.1.0",
"@types/react-dom": "^19.1.0",
"@types/react-window": "^1.8.8",
"cross-env": "^7.0.3",
"eslint": "^8",
"eslint": "^9.39.2",
"eslint-config-next": "^15.0.3",
"jest": "^30.2.0",
"jest-environment-jsdom": "^30.2.0",
"ts-jest": "^29.4.6",
"typescript": "^5.8.3"
},
"refine": {

View File

@@ -16,10 +16,12 @@ import {
Analytics as AnalyticsIcon,
Search as SearchIcon,
MyLocation as MyLocationIcon,
Handyman as HandymanIcon,
} from "@mui/icons-material";
import AnalysisParameters from "./AnalysisParameters";
import SchemeQuery from "./SchemeQuery";
import LocationResults from "./LocationResults";
import ValveIsolation from "./ValveIsolation";
import ContaminantAnalysisParameters from "../ContaminantSimulation/AnalysisParameters";
import ContaminantSchemeQuery from "../ContaminantSimulation/SchemeQuery";
import ContaminantResultsPanel from "../ContaminantSimulation/ResultsPanel";
@@ -248,6 +250,13 @@ const BurstPipeAnalysisPanel: React.FC<BurstPipeAnalysisPanelProps> = ({
iconPosition="start"
label={isBurstMode ? "定位结果" : "模拟结果"}
/>
{isBurstMode && (
<Tab
icon={<HandymanIcon fontSize="small" />}
iconPosition="start"
label="关阀分析"
/>
)}
</Tabs>
</Box>
@@ -279,6 +288,12 @@ const BurstPipeAnalysisPanel: React.FC<BurstPipeAnalysisPanelProps> = ({
<ContaminantResultsPanel schemeName={data?.schemeName} />
)}
</TabPanel>
{isBurstMode && (
<TabPanel value={currentTab} index={3}>
<ValveIsolation />
</TabPanel>
)}
</Box>
</Drawer>
</>

View File

@@ -0,0 +1,119 @@
"use client";
import React, { useState } from 'react';
import { Box, TextField, Button, Typography, Card, CardContent, Chip } from '@mui/material';
import axios from 'axios';
import { config, NETWORK_NAME } from '@config/config';
import { ValveIsolationResult } from './types';
import { useNotification } from "@refinedev/core";
const ValveIsolation: React.FC = () => {
const [pipeId, setPipeId] = useState('');
const [loading, setLoading] = useState(false);
const [result, setResult] = useState<ValveIsolationResult | null>(null);
const { open } = useNotification();
const handleAnalyze = async () => {
if (!pipeId.trim()) {
open?.({ type: 'error', message: '请输入管段ID' });
return;
}
setLoading(true);
setResult(null);
try {
const response = await axios.get(`${config.BACKEND_URL}/api/v1/valve_isolation_analysis`, {
params: {
network: NETWORK_NAME,
accident_element: pipeId
}
});
setResult(response.data);
open?.({ type: 'success', message: '分析成功' });
} catch (error) {
console.error(error);
open?.({ type: 'error', message: '分析失败', description: '无法获取关阀分析结果' });
} finally {
setLoading(false);
}
};
return (
<Box className="flex flex-col h-full p-4">
{/* Input Section */}
<Box className="mb-4 flex gap-2 items-end">
<TextField
label="爆管管段ID"
variant="outlined"
size="small"
fullWidth
value={pipeId}
onChange={(e) => setPipeId(e.target.value)}
placeholder="请输入管段ID"
/>
<Button
variant="contained"
onClick={handleAnalyze}
disabled={loading}
className="bg-blue-600 h-[40px] min-w-[100px]"
>
{loading ? '分析中' : '开始分析'}
</Button>
</Box>
{/* Results Section */}
{result && (
<Box className="flex-1 overflow-auto space-y-4">
<Card variant="outlined">
<CardContent className="p-3">
<Box className="flex justify-between items-center mb-2">
<Typography variant="subtitle1" fontWeight="bold"></Typography>
<Chip
label={result.isolatable ? "可隔离" : "不可隔离"}
color={result.isolatable ? "success" : "error"}
size="small"
/>
</Box>
<Typography variant="body2" color="text.secondary">: {result.accident_type}</Typography>
<Typography variant="body2" color="text.secondary">: {result.affected_nodes?.length || 0}</Typography>
</CardContent>
</Card>
<Box>
<Typography variant="subtitle2" className="mb-2"> ({result.must_close_valves?.length || 0})</Typography>
{result.must_close_valves?.length > 0 ? (
<Box className="flex flex-wrap gap-2">
{result.must_close_valves.map(v => (
<Chip key={v} label={v} color="error" variant="outlined" size="small" />
))}
</Box>
) : <Typography variant="caption" color="text.secondary"></Typography>}
</Box>
<Box>
<Typography variant="subtitle2" className="mb-2"> ({result.optional_valves?.length || 0})</Typography>
{result.optional_valves?.length > 0 ? (
<Box className="flex flex-wrap gap-2">
{result.optional_valves.map(v => (
<Chip key={v} label={v} color="warning" variant="outlined" size="small" />
))}
</Box>
) : <Typography variant="caption" color="text.secondary"></Typography>}
</Box>
<Box>
<Typography variant="subtitle2" className="mb-2"> ({result.affected_nodes?.length || 0})</Typography>
{result.affected_nodes?.length > 0 ? (
<Box className="bg-gray-50 p-2 rounded max-h-40 overflow-auto">
<Typography variant="caption" className="break-all">
{result.affected_nodes.join(', ')}
</Typography>
</Box>
) : <Typography variant="caption" color="text.secondary"></Typography>}
</Box>
</Box>
)}
</Box>
);
};
export default ValveIsolation;

View File

@@ -36,3 +36,12 @@ export interface LocationResult {
detect_time: string;
locate_result: string[] | null;
}
export interface ValveIsolationResult {
accident_element: string;
accident_type: string;
affected_nodes: string[];
must_close_valves: string[];
optional_valves: string[];
isolatable: boolean;
}

View File

@@ -23,11 +23,38 @@
}
],
"paths": {
"@*": [
"./src/*"
],
"@pages/*": [
"./pages/*"
],
"@app/*": [
"./src/app/*"
],
"@assets/*": [
"./src/assets/*"
],
"@components/*": [
"./src/components/*"
],
"@config/*": [
"./src/config/*"
],
"@contexts/*": [
"./src/contexts/*"
],
"@interfaces/*": [
"./src/interfaces/*"
],
"@libs/*": [
"./src/libs/*"
],
"@providers/*": [
"./src/providers/*"
],
"@utils/*": [
"./src/utils/*"
],
"@/*": [
"./src/*"
]
},
"incremental": true