功能: 完善风电功率方案计算
- 新增计算方案保存与方案二筛选参数 - 支持方案二桨角规则和有效数据导出 - 优化功率曲线绘制和设计功率展示
This commit is contained in:
@@ -154,6 +154,102 @@
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.schemePanel {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.schemeGrid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(240px, 0.38fr) minmax(0, 1fr);
|
||||
gap: 14px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.schemeDescriptionControl {
|
||||
display: grid;
|
||||
grid-template-columns: 72px minmax(0, 1fr);
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.schemeDescriptionControl span {
|
||||
color: #cbd5e1;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.schemeDescriptionControl input {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
height: 36px;
|
||||
border: 1px solid #343449;
|
||||
border-radius: 7px;
|
||||
background: #0f0f18;
|
||||
color: #e5e7eb;
|
||||
padding: 0 10px;
|
||||
font-size: 14px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.schemeDescriptionControl input:focus {
|
||||
border-color: #6366f1;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.schemeDescriptionControl input:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.62;
|
||||
}
|
||||
|
||||
.schemeParamGrid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.schemeParamGrid input {
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
height: 36px;
|
||||
border: 1px solid #343449;
|
||||
border-radius: 7px;
|
||||
background: #0f0f18;
|
||||
color: #e5e7eb;
|
||||
padding: 0 10px;
|
||||
font-size: 14px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.schemeParamGrid input:focus {
|
||||
border-color: #6366f1;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.schemeParamGrid input:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.62;
|
||||
}
|
||||
|
||||
.schemeParamGrid input::placeholder {
|
||||
color: #5f6673;
|
||||
}
|
||||
|
||||
.schemeFooter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.schemeFooter span {
|
||||
min-height: 18px;
|
||||
color: #86efac;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.designActions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -571,7 +667,9 @@ td {
|
||||
}
|
||||
|
||||
.homeHeader,
|
||||
.workspace {
|
||||
.workspace,
|
||||
.schemeGrid,
|
||||
.schemeParamGrid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ import * as XLSX from 'xlsx';
|
||||
import {
|
||||
deleteWindJob,
|
||||
finishWindJob,
|
||||
getWindSchemes,
|
||||
saveWindSchemeDescription,
|
||||
startWindJob,
|
||||
uploadWindChunk,
|
||||
} from '../utils/api';
|
||||
@@ -15,6 +17,9 @@ const REQUIRED_FIELDS = [
|
||||
{ key: 'wind_speed', label: '风速' },
|
||||
{ key: 'active_power', label: '有功功率' },
|
||||
{ key: 'generator_speed', label: '发电机转速' },
|
||||
{ key: 'blade_pitch_1', label: '1#叶片角度' },
|
||||
{ key: 'blade_pitch_2', label: '2#叶片角度' },
|
||||
{ key: 'blade_pitch_3', label: '3#叶片角度' },
|
||||
];
|
||||
|
||||
const FIELD_HINTS = {
|
||||
@@ -23,6 +28,9 @@ const FIELD_HINTS = {
|
||||
wind_speed: ['风速', '平均风速', 'wind speed', 'windspeed'],
|
||||
active_power: ['平均有功功率', '有功功率', 'active power', 'power'],
|
||||
generator_speed: ['发电机转速', '平均发电机转速', 'generator speed', 'rpm'],
|
||||
blade_pitch_1: ['1#叶片变桨角度', '1#叶片角度', '1叶片变桨角度', '1叶片角度', '1号叶片角度', '叶片1角度', '桨角1', '变桨角1', 'pitch 1', 'pitch angle 1', 'blade pitch 1'],
|
||||
blade_pitch_2: ['2#叶片变桨角度', '2#叶片角度', '2叶片变桨角度', '2叶片角度', '2号叶片角度', '叶片2角度', '桨角2', '变桨角2', 'pitch 2', 'pitch angle 2', 'blade pitch 2'],
|
||||
blade_pitch_3: ['3#叶片变桨角度', '3#叶片角度', '3叶片变桨角度', '3叶片角度', '3号叶片角度', '叶片3角度', '桨角3', '变桨角3', 'pitch 3', 'pitch angle 3', 'blade pitch 3'],
|
||||
};
|
||||
|
||||
const FIELD_EXCLUDES = {
|
||||
@@ -41,6 +49,27 @@ const DESIGN_FIELD_HINTS = {
|
||||
|
||||
const CHUNK_SIZE = 4000;
|
||||
const CHART_HEIGHT = 540;
|
||||
const DEFAULT_SCHEME_ID = 'scheme_one';
|
||||
const SCHEME_TWO_ID = 'scheme_two';
|
||||
const DEFAULT_SCHEMES = [
|
||||
{ id: 'scheme_one', name: '方案一', description: '通用方案' },
|
||||
{
|
||||
id: SCHEME_TWO_ID,
|
||||
name: '方案二',
|
||||
description: '桨角筛选方案,使用三支叶片角度平均值、600s 平均风速和手填转速/功率参数',
|
||||
parameters: {
|
||||
grid_connected_speed: 1030,
|
||||
rated_generator_speed: 1755,
|
||||
rated_power: 2000,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const DEFAULT_SCHEME_TWO_PARAMS = {
|
||||
grid_connected_speed: '1030',
|
||||
rated_generator_speed: '1755',
|
||||
rated_power: '2000',
|
||||
};
|
||||
|
||||
function createDesignRows(points) {
|
||||
const source = points?.length ? points : [{ wind_speed: '', design_power: '' }];
|
||||
@@ -289,6 +318,9 @@ function buildStandardRows(files, mapping) {
|
||||
wind_speed: normalizeNumber(getCell(row, localIndexes.wind_speed)),
|
||||
active_power: normalizeNumber(getCell(row, localIndexes.active_power)),
|
||||
generator_speed: normalizeNumber(getCell(row, localIndexes.generator_speed)),
|
||||
blade_pitch_1: normalizeNumber(getCell(row, localIndexes.blade_pitch_1)),
|
||||
blade_pitch_2: normalizeNumber(getCell(row, localIndexes.blade_pitch_2)),
|
||||
blade_pitch_3: normalizeNumber(getCell(row, localIndexes.blade_pitch_3)),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -296,6 +328,69 @@ function buildStandardRows(files, mapping) {
|
||||
return { rows, indexes };
|
||||
}
|
||||
|
||||
function formatTimestampForFile(date = new Date()) {
|
||||
return `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}`
|
||||
+ `${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}`;
|
||||
}
|
||||
|
||||
function sanitizeFileName(value) {
|
||||
return String(value || '未选择风机').replace(/[\\/:*?"<>|]/g, '_');
|
||||
}
|
||||
|
||||
function exportValidRowsToExcel(fanId, rows) {
|
||||
const exportRows = (rows || []).map((row) => ({
|
||||
风机编号: row.fan_id || fanId || '',
|
||||
采样时间: row.time || '',
|
||||
平均功率: row.active_power ?? '',
|
||||
平均转速: row.generator_speed ?? '',
|
||||
平均风速: row.wind_speed ?? '',
|
||||
'3个叶片变桨角平均值': row.pitch_angle_average ?? '',
|
||||
}));
|
||||
const worksheet = XLSX.utils.json_to_sheet(exportRows);
|
||||
const workbook = XLSX.utils.book_new();
|
||||
XLSX.utils.book_append_sheet(workbook, worksheet, '有效数据');
|
||||
XLSX.writeFile(
|
||||
workbook,
|
||||
`有效数据_${sanitizeFileName(fanId)}_${formatTimestampForFile()}.xlsx`,
|
||||
);
|
||||
}
|
||||
|
||||
function interpolateDesignPower(points, windSpeed) {
|
||||
if (!points.length) return null;
|
||||
if (windSpeed < points[0].wind_speed || windSpeed > points[points.length - 1].wind_speed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (let index = 0; index < points.length; index += 1) {
|
||||
const point = points[index];
|
||||
if (Math.abs(point.wind_speed - windSpeed) < 0.000001) {
|
||||
return point.design_power;
|
||||
}
|
||||
if (point.wind_speed > windSpeed && index > 0) {
|
||||
const left = points[index - 1];
|
||||
const right = point;
|
||||
const span = right.wind_speed - left.wind_speed;
|
||||
if (span <= 0) return left.design_power;
|
||||
const ratio = (windSpeed - left.wind_speed) / span;
|
||||
return left.design_power + (right.design_power - left.design_power) * ratio;
|
||||
}
|
||||
}
|
||||
|
||||
return points[points.length - 1].design_power;
|
||||
}
|
||||
|
||||
function schemeParamsToState(parameters) {
|
||||
return {
|
||||
grid_connected_speed: String(
|
||||
parameters?.grid_connected_speed ?? DEFAULT_SCHEME_TWO_PARAMS.grid_connected_speed,
|
||||
),
|
||||
rated_generator_speed: String(
|
||||
parameters?.rated_generator_speed ?? DEFAULT_SCHEME_TWO_PARAMS.rated_generator_speed,
|
||||
),
|
||||
rated_power: String(parameters?.rated_power ?? DEFAULT_SCHEME_TWO_PARAMS.rated_power),
|
||||
};
|
||||
}
|
||||
|
||||
function SummaryCards({ summary }) {
|
||||
if (!summary) return null;
|
||||
const cards = [
|
||||
@@ -352,15 +447,12 @@ function PowerCurveChart({
|
||||
const actualByWindSpeed = new Map(
|
||||
sortedCurve.map((point) => [point.wind_speed.toFixed(6), point.average_power]),
|
||||
);
|
||||
const designByWindSpeed = new Map(
|
||||
sortedDesign.map((point) => [point.wind_speed.toFixed(6), point.design_power]),
|
||||
);
|
||||
const curveX = Array.from(new Set([
|
||||
...sortedCurve.map((point) => point.wind_speed),
|
||||
...sortedDesign.map((point) => point.wind_speed),
|
||||
])).sort((left, right) => left - right);
|
||||
const actualY = curveX.map((windSpeed) => actualByWindSpeed.get(windSpeed.toFixed(6)) ?? null);
|
||||
const designY = curveX.map((windSpeed) => designByWindSpeed.get(windSpeed.toFixed(6)) ?? null);
|
||||
const designY = curveX.map((windSpeed) => interpolateDesignPower(sortedDesign, windSpeed));
|
||||
const maxScatterPower = validScatter.reduce(
|
||||
(max, point) => Math.max(max, point.active_power),
|
||||
0,
|
||||
@@ -409,6 +501,7 @@ function PowerCurveChart({
|
||||
stroke: '#ef4444',
|
||||
width: 2.5,
|
||||
paths: uPlot.paths.spline(),
|
||||
spanGaps: true,
|
||||
points: { show: false },
|
||||
},
|
||||
{
|
||||
@@ -416,6 +509,7 @@ function PowerCurveChart({
|
||||
stroke: '#22c55e',
|
||||
width: 2.2,
|
||||
paths: uPlot.paths.spline(),
|
||||
spanGaps: true,
|
||||
points: { show: false },
|
||||
},
|
||||
],
|
||||
@@ -430,7 +524,7 @@ function PowerCurveChart({
|
||||
const y = u.valToPos(point.active_power, 'y', true);
|
||||
if (Number.isFinite(x) && Number.isFinite(y)) {
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, 1.6, 0, Math.PI * 2);
|
||||
ctx.arc(x, y, 2, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
@@ -441,7 +535,7 @@ function PowerCurveChart({
|
||||
const y = u.valToPos(point.active_power, 'y', true);
|
||||
if (Number.isFinite(x) && Number.isFinite(y)) {
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, 1.9, 0, Math.PI * 2);
|
||||
ctx.arc(x, y, 2, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
@@ -484,9 +578,17 @@ export default function HomePage() {
|
||||
const [readingDesign, setReadingDesign] = useState(false);
|
||||
const [designRows, setDesignRows] = useState(() => createDesignRows());
|
||||
const [designMeta, setDesignMeta] = useState(null);
|
||||
const [schemes, setSchemes] = useState(DEFAULT_SCHEMES);
|
||||
const [selectedSchemeId, setSelectedSchemeId] = useState(DEFAULT_SCHEME_ID);
|
||||
const [schemeDescription, setSchemeDescription] = useState(DEFAULT_SCHEMES[0].description);
|
||||
const [savingScheme, setSavingScheme] = useState(false);
|
||||
const [schemeMessage, setSchemeMessage] = useState('');
|
||||
const [schemeTwoParams, setSchemeTwoParams] = useState(DEFAULT_SCHEME_TWO_PARAMS);
|
||||
|
||||
const headers = files[0]?.headers || [];
|
||||
const missingFields = REQUIRED_FIELDS.filter((field) => !mapping[field.key]);
|
||||
const selectedScheme = schemes.find((scheme) => scheme.id === selectedSchemeId) || schemes[0];
|
||||
const isSchemeTwo = selectedSchemeId === SCHEME_TWO_ID;
|
||||
const selectedPoints = selectedFan && result?.curves ? result.curves[selectedFan] || [] : [];
|
||||
const selectedBins = selectedFan && result?.bins ? result.bins[selectedFan] || [] : [];
|
||||
const selectedScatter = selectedFan && result?.scatter_points
|
||||
@@ -526,6 +628,95 @@ export default function HomePage() {
|
||||
.sort((left, right) => left.wind_speed - right.wind_speed);
|
||||
}, [designRows]);
|
||||
|
||||
useEffect(() => {
|
||||
let canceled = false;
|
||||
|
||||
async function loadSchemes() {
|
||||
try {
|
||||
const data = await getWindSchemes();
|
||||
if (canceled) return;
|
||||
|
||||
const loadedSchemes = data.schemes?.length ? data.schemes : DEFAULT_SCHEMES;
|
||||
const defaultSchemeId = data.default_scheme_id || DEFAULT_SCHEME_ID;
|
||||
const defaultScheme = loadedSchemes.find((scheme) => scheme.id === defaultSchemeId)
|
||||
|| loadedSchemes[0];
|
||||
setSchemes(loadedSchemes);
|
||||
setSelectedSchemeId(defaultScheme.id);
|
||||
setSchemeDescription(defaultScheme.description || '');
|
||||
const schemeTwo = loadedSchemes.find((scheme) => scheme.id === SCHEME_TWO_ID);
|
||||
setSchemeTwoParams(schemeParamsToState(schemeTwo?.parameters));
|
||||
} catch (err) {
|
||||
if (!canceled) {
|
||||
setSchemeMessage(err.message || '读取方案配置失败,已使用默认方案');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loadSchemes();
|
||||
return () => {
|
||||
canceled = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
function handleSchemeChange(value) {
|
||||
const nextScheme = schemes.find((scheme) => scheme.id === value);
|
||||
setSelectedSchemeId(value);
|
||||
setSchemeDescription(nextScheme?.description || '');
|
||||
if (value === SCHEME_TWO_ID) {
|
||||
setSchemeTwoParams(schemeParamsToState(nextScheme?.parameters));
|
||||
}
|
||||
setSchemeMessage('');
|
||||
}
|
||||
|
||||
function handleSchemeTwoParamChange(field, value) {
|
||||
setSchemeTwoParams((prev) => ({
|
||||
...prev,
|
||||
[field]: value,
|
||||
}));
|
||||
}
|
||||
|
||||
async function handleSaveSchemeDescription() {
|
||||
if (!selectedScheme) return;
|
||||
|
||||
setSavingScheme(true);
|
||||
setError('');
|
||||
setSchemeMessage('');
|
||||
try {
|
||||
const gridConnectedSpeed = normalizeNumber(schemeTwoParams.grid_connected_speed);
|
||||
const ratedGeneratorSpeed = normalizeNumber(schemeTwoParams.rated_generator_speed);
|
||||
const ratedPower = normalizeNumber(schemeTwoParams.rated_power);
|
||||
if (selectedScheme.id === SCHEME_TWO_ID &&
|
||||
(!Number.isFinite(gridConnectedSpeed) || gridConnectedSpeed <= 0 ||
|
||||
!Number.isFinite(ratedGeneratorSpeed) || ratedGeneratorSpeed <= 0 ||
|
||||
!Number.isFinite(ratedPower) || ratedPower <= 0)) {
|
||||
throw new Error('方案二参数必须为正数');
|
||||
}
|
||||
const data = await saveWindSchemeDescription(selectedScheme.id, {
|
||||
description: schemeDescription,
|
||||
...(selectedScheme.id === SCHEME_TWO_ID ? {
|
||||
parameters: {
|
||||
grid_connected_speed: gridConnectedSpeed,
|
||||
rated_generator_speed: ratedGeneratorSpeed,
|
||||
rated_power: ratedPower,
|
||||
},
|
||||
} : {}),
|
||||
});
|
||||
const savedScheme = data.scheme;
|
||||
setSchemes((prev) => prev.map((scheme) => (
|
||||
scheme.id === savedScheme.id ? { ...scheme, ...savedScheme } : scheme
|
||||
)));
|
||||
setSchemeDescription(savedScheme.description || '');
|
||||
if (savedScheme.id === SCHEME_TWO_ID) {
|
||||
setSchemeTwoParams(schemeParamsToState(savedScheme.parameters));
|
||||
}
|
||||
setSchemeMessage('已保存');
|
||||
} catch (err) {
|
||||
setError(err.message || '保存失败');
|
||||
} finally {
|
||||
setSavingScheme(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleFilesChange(event) {
|
||||
const selectedFiles = Array.from(event.target.files || []);
|
||||
if (!selectedFiles.length) return;
|
||||
@@ -605,6 +796,16 @@ export default function HomePage() {
|
||||
setError('请先上传文件并完成所有字段映射');
|
||||
return;
|
||||
}
|
||||
const gridConnectedSpeed = normalizeNumber(schemeTwoParams.grid_connected_speed);
|
||||
const ratedGeneratorSpeed = normalizeNumber(schemeTwoParams.rated_generator_speed);
|
||||
const ratedPower = normalizeNumber(schemeTwoParams.rated_power);
|
||||
if (isSchemeTwo &&
|
||||
(!Number.isFinite(gridConnectedSpeed) || gridConnectedSpeed <= 0 ||
|
||||
!Number.isFinite(ratedGeneratorSpeed) || ratedGeneratorSpeed <= 0 ||
|
||||
!Number.isFinite(ratedPower) || ratedPower <= 0)) {
|
||||
setError('方案二需要填写并网转速、额定转速和额定功率,且必须为正数');
|
||||
return;
|
||||
}
|
||||
|
||||
let jobId = '';
|
||||
setSubmitting(true);
|
||||
@@ -647,6 +848,12 @@ export default function HomePage() {
|
||||
iqr_upper_multiplier: 2,
|
||||
minimum_generator_speed: 1,
|
||||
generator_speed_k: 0.9,
|
||||
scheme_id: selectedSchemeId,
|
||||
...(isSchemeTwo ? {
|
||||
grid_connected_speed: gridConnectedSpeed,
|
||||
rated_generator_speed: ratedGeneratorSpeed,
|
||||
rated_power: ratedPower,
|
||||
} : {}),
|
||||
},
|
||||
});
|
||||
setResult(calculation);
|
||||
@@ -691,6 +898,102 @@ export default function HomePage() {
|
||||
{error && <div className="alert error">{error}</div>}
|
||||
{progress && <div className="alert info">{progress}</div>}
|
||||
|
||||
<section className="panel schemePanel">
|
||||
<div className="panelHeader">
|
||||
<div>
|
||||
<h2>计算方案</h2>
|
||||
<p className="panelHint">选择本次计算使用的清洗方案,方案描述可保存为服务端共享配置。</p>
|
||||
</div>
|
||||
<span>{selectedScheme?.name || '方案一'}</span>
|
||||
</div>
|
||||
<div className="schemeGrid">
|
||||
<label className="fieldControl">
|
||||
<span>方案选择</span>
|
||||
<select
|
||||
value={selectedSchemeId}
|
||||
onChange={(event) => handleSchemeChange(event.target.value)}
|
||||
disabled={submitting}
|
||||
>
|
||||
{schemes.map((scheme) => (
|
||||
<option key={scheme.id} value={scheme.id}>{scheme.name}</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label className="schemeDescriptionControl">
|
||||
<span>方案描述</span>
|
||||
<input
|
||||
type="text"
|
||||
value={schemeDescription}
|
||||
onChange={(event) => {
|
||||
setSchemeDescription(event.target.value);
|
||||
setSchemeMessage('');
|
||||
}}
|
||||
disabled={submitting || savingScheme}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
{isSchemeTwo && (
|
||||
<div className="schemeParamGrid">
|
||||
<label className="fieldControl">
|
||||
<span>并网转速 (rpm)</span>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.01"
|
||||
value={schemeTwoParams.grid_connected_speed}
|
||||
onChange={(event) => handleSchemeTwoParamChange(
|
||||
'grid_connected_speed',
|
||||
event.target.value,
|
||||
)}
|
||||
disabled={submitting}
|
||||
placeholder="请输入"
|
||||
/>
|
||||
</label>
|
||||
<label className="fieldControl">
|
||||
<span>额定转速 (rpm)</span>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.01"
|
||||
value={schemeTwoParams.rated_generator_speed}
|
||||
onChange={(event) => handleSchemeTwoParamChange(
|
||||
'rated_generator_speed',
|
||||
event.target.value,
|
||||
)}
|
||||
disabled={submitting}
|
||||
placeholder="请输入"
|
||||
/>
|
||||
</label>
|
||||
<label className="fieldControl">
|
||||
<span>额定功率 (kW)</span>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.1"
|
||||
value={schemeTwoParams.rated_power}
|
||||
onChange={(event) => handleSchemeTwoParamChange(
|
||||
'rated_power',
|
||||
event.target.value,
|
||||
)}
|
||||
disabled={submitting}
|
||||
placeholder="请输入"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
<div className="schemeFooter">
|
||||
<span>{schemeMessage}</span>
|
||||
<button
|
||||
className="secondaryButton"
|
||||
type="button"
|
||||
onClick={handleSaveSchemeDescription}
|
||||
disabled={submitting || savingScheme}
|
||||
>
|
||||
{savingScheme ? '保存中' : '保存'}
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="panel designPowerPanel">
|
||||
<div className="panelHeader">
|
||||
<div>
|
||||
@@ -860,6 +1163,12 @@ export default function HomePage() {
|
||||
<span>计算完成</span>
|
||||
</div>
|
||||
<SummaryCards summary={result.summary} />
|
||||
{result.scheme && (
|
||||
<div className="paramStrip">
|
||||
<span>方案:{result.scheme.name}</span>
|
||||
<span>{result.scheme.description}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="reasonList">
|
||||
{Object.entries(result.summary?.invalid_reasons || {}).map(([reason, count]) => (
|
||||
<span key={reason}>{reason}: {Number(count).toLocaleString()}</span>
|
||||
@@ -883,6 +1192,14 @@ export default function HomePage() {
|
||||
>
|
||||
{showFiltered ? '隐藏滤除点' : '显示滤除点'}
|
||||
</button>
|
||||
<button
|
||||
className="secondaryButton"
|
||||
type="button"
|
||||
onClick={() => exportValidRowsToExcel(selectedFan, selectedScatter)}
|
||||
disabled={!selectedScatter.length}
|
||||
>
|
||||
导出有效数据
|
||||
</button>
|
||||
<select
|
||||
className="fanSelect"
|
||||
value={selectedFan}
|
||||
|
||||
@@ -54,6 +54,27 @@ export function getHealth() {
|
||||
return request('/system/health');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取计算方案列表
|
||||
* @returns {Promise<{default_scheme_id: string, schemes: Array<object>}>}
|
||||
*/
|
||||
export function getWindSchemes() {
|
||||
return request('/wind/schemes');
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存计算方案描述
|
||||
* @param {string} schemeId
|
||||
* @param {{description: string}} payload
|
||||
* @returns {Promise<{scheme: object}>}
|
||||
*/
|
||||
export function saveWindSchemeDescription(schemeId, payload) {
|
||||
return request(`/wind/schemes/${encodeURIComponent(schemeId)}/description`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建风功率计算任务
|
||||
* @param {{files: Array<{file_name: string, row_count: number}>, mapping: object}} payload
|
||||
|
||||
Reference in New Issue
Block a user