Commit 6f2731f4 authored by fenghen777's avatar fenghen777

feat: 仿真时长参数支持 + 按钮布局优化 + 智能状态重置

parent abe22eec
This diff is collapsed.
......@@ -84,9 +84,17 @@
**请求:**
```json
{ "model_name": "Circuit" }
{
"model_name": "Circuit",
"stop_time": 2.0
}
```
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| model_name | string | ✅ | 模型名称 |
| stop_time | number | - | 仿真终止时间(秒),默认 1.0 |
**成功响应** (`code: 0`):
```json
{
......@@ -138,6 +146,7 @@
"sim_mode": "switch",
"ports": ["p", "n"],
"fmu_var_prefix": "idealSwitch_1",
"control_var": "idealSwitch_1.closed",
"topic": "/Circuit_1/idealSwitch_1",
"hw_label": "开关",
"hw_node_id": "new-device-3"
......@@ -162,6 +171,7 @@
| hardware_ports[].topic | string | - | ROS2 话题前缀 |
| hardware_ports[].hw_label | string | - | 节点显示名称 |
| hardware_ports[].hw_node_id | string | - | 前端节点 ID |
| hardware_ports[].control_var | string | - | 控制变量全名,如 `idealSwitch_1.closed` |
| step_size | number | - | 仿真步长(秒),默认 0.001 |
| duration | number | - | 仿真时长(秒),默认 10.0 |
......
......@@ -54,6 +54,7 @@ export default function ProjectPanel() {
const [showCompileLog, setShowCompileLog] = useState(false);
const [showExecuteLog, setShowExecuteLog] = useState(false);
const [showResultsMode, setShowResultsMode] = useState(null); // null | 'sim' | 'hil'
const [simDuration, setSimDuration] = useState(1);
const [hilDuration, setHilDuration] = useState(10);
const activeProject = projects.find(p => p.id === activeProjectId) || null;
......@@ -228,12 +229,12 @@ export default function ProjectPanel() {
/>
</div>
{/* 操作按钮 */}
<div className={styles.simBtnRow}>
{/* 编译按钮 */}
<button
className={`${styles.simBtn} ${styles.compile}`}
onClick={(e) => { e.stopPropagation(); compileProject(p.id); }}
disabled={p.compileStatus === 'compiling'}
style={{ width: '100%', marginBottom: 6 }}
>
{p.compileStatus === 'compiling' ? (
<><span className={styles.spinner}></span> 编译中…</>
......@@ -241,11 +242,33 @@ export default function ProjectPanel() {
<>▶ 编译模型</>
)}
</button>
{/* 普通仿真: 时长 + 执行按钮 */}
<div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 4 }}>
<label style={{ fontSize: 11, color: '#888', whiteSpace: 'nowrap' }}>仿真时长:</label>
<input
type="number"
min="0.1" max="3600" step="0.1"
value={simDuration}
onChange={(e) => setSimDuration(e.target.value === '' ? '' : Number(e.target.value))}
onBlur={() => { if (!simDuration || simDuration <= 0) setSimDuration(1); }}
onClick={(e) => e.stopPropagation()}
disabled={p.compileStatus !== 'success'}
style={{
flex: 1, padding: '3px 6px', fontSize: 11,
background: 'rgba(255,255,255,0.06)', border: '1px solid rgba(255,255,255,0.12)',
borderRadius: 4, color: '#ccc', outline: 'none',
opacity: p.compileStatus !== 'success' ? 0.4 : 1,
}}
/>
<span style={{ fontSize: 11, color: '#666' }}></span>
</div>
<button
className={`${styles.simBtn} ${styles.execute}`}
onClick={(e) => { e.stopPropagation(); executeProject(p.id); }}
onClick={(e) => { e.stopPropagation(); executeProject(p.id, { stopTime: simDuration }); }}
disabled={p.compileStatus !== 'success' || p.executeStatus === 'running'}
title={p.compileStatus !== 'success' ? '请先编译成功' : '下发等效设备执行'}
style={{ width: '100%', marginBottom: 6 }}
>
{p.executeStatus === 'running' ? (
<><span className={styles.spinner}></span> 下发中…</>
......@@ -253,27 +276,28 @@ export default function ProjectPanel() {
<>⏵ 下发等效设备执行</>
)}
</button>
</div>
{/* 半实物仿真按钮 */}
{/* 半实物仿真: 时长 + HIL 按钮 */}
{(() => {
const hwCount = (p.nodes || []).filter(n => n.data?.isHardware).length;
const hilDisabled = hwCount === 0 || p.compileStatus !== 'success' || p.hilStatus === 'starting' || p.hilStatus === 'running';
return (
<>
{/* 仿真时长输入 */}
{hwCount > 0 && p.hilStatus !== 'running' && (
{hwCount > 0 && (
<div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 4 }}>
<label style={{ fontSize: 11, color: '#888', whiteSpace: 'nowrap' }}>仿真时长:</label>
<label style={{ fontSize: 11, color: '#888', whiteSpace: 'nowrap' }}>HIL时长:</label>
<input
type="number"
min="1" max="3600" step="1"
value={hilDuration}
onChange={(e) => setHilDuration(Number(e.target.value) || 10)}
onClick={(e) => e.stopPropagation()}
disabled={hilDisabled}
style={{
flex: 1, padding: '3px 6px', fontSize: 11,
background: 'rgba(255,255,255,0.06)', border: '1px solid rgba(255,255,255,0.12)',
borderRadius: 4, color: '#ccc', outline: 'none',
opacity: hilDisabled ? 0.4 : 1,
}}
/>
<span style={{ fontSize: 11, color: '#666' }}></span>
......@@ -287,8 +311,8 @@ export default function ProjectPanel() {
borderColor: hwCount > 0 ? 'rgba(251,146,60,0.3)' : undefined,
color: hwCount > 0 ? '#fb923c' : undefined,
}}
disabled={hwCount === 0 || p.hilStatus === 'starting' || p.hilStatus === 'running'}
title={hwCount === 0 ? '请先在属性面板中将节点标记为实物' : `${hwCount} 个实物节点`}
disabled={hilDisabled}
title={p.compileStatus !== 'success' ? '请先编译成功' : hwCount === 0 ? '请先在属性面板中将节点标记为实物' : `${hwCount} 个实物节点`}
onClick={(e) => { e.stopPropagation(); startHil(p.id, { duration: hilDuration }); }}
>
{p.hilStatus === 'starting' ? (
......
......@@ -53,9 +53,18 @@ const useProjectStore = create((set, get) => ({
if (activeProjectId) {
// 更新已有项目
const prev = projects.find(p => p.id === activeProjectId);
// 仅当节点/连线发生变化时重置编译/执行/HIL 状态
const nodesChanged = JSON.stringify(prev?.nodes) !== JSON.stringify(nodes)
|| JSON.stringify(prev?.edges) !== JSON.stringify(edges);
const resetFields = nodesChanged ? {
compileStatus: 'none', compileResult: null,
executeStatus: 'none', executeResult: null,
hilStatus: 'none', hilResult: null,
} : {};
const updated = projects.map(p =>
p.id === activeProjectId
? { ...p, nodes, edges, updatedAt: now, name: name || p.name }
? { ...p, nodes, edges, updatedAt: now, name: name || p.name, ...resetFields }
: p
);
persistProjects(updated);
......@@ -211,7 +220,7 @@ const useProjectStore = create((set, get) => ({
},
/** 执行仿真 — 调用后端 API(仅编译成功后可执行) */
executeProject: async (id) => {
executeProject: async (id, { stopTime = 1.0 } = {}) => {
const { projects } = get();
const project = projects.find(p => p.id === id);
if (!project || project.compileStatus !== 'success') return;
......@@ -228,7 +237,7 @@ const useProjectStore = create((set, get) => ({
try {
// 响应格式: { code, message, data: { sim_log, csv_data } }
const resp = await executeModel(project.modelName || 'Circuit');
const resp = await executeModel(project.modelName || 'Circuit', stopTime);
const isOk = resp.code === 0;
setStatus({
executeStatus: isOk ? 'success' : 'error',
......@@ -251,7 +260,6 @@ const useProjectStore = create((set, get) => ({
/** 启动半实物仿真 */
startHil: async (id, { duration = 10, stepSize = 0.001 } = {}) => {
get().saveProject();
const { projects } = get();
const project = projects.find(p => p.id === id);
......
......@@ -39,10 +39,10 @@ export async function compileModel(moCode, modelName) {
* @param {string} modelName - 已编译的模型名称
* @returns {Promise<{code, message, data: {sim_log, model_name, csv_data?}}>}
*/
export async function executeModel(modelName) {
export async function executeModel(modelName, stopTime = 1.0) {
return request('/execute', {
method: 'POST',
body: JSON.stringify({ model_name: modelName }),
body: JSON.stringify({ model_name: modelName, stop_time: stopTime }),
});
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment