Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
E
EPlanVisualizer
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
嵇洲
EPlanVisualizer
Commits
6f2731f4
Commit
6f2731f4
authored
Mar 17, 2026
by
fenghen777
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 仿真时长参数支持 + 按钮布局优化 + 智能状态重置
parent
abe22eec
Changes
5
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
80 additions
and
38 deletions
+80
-38
半实物仿真方案.md
docs/半实物仿真方案.md
+0
-0
接口文档.md
docs/接口文档.md
+11
-1
ProjectPanel.jsx
src/components/ProjectPanel/ProjectPanel.jsx
+55
-31
useProjectStore.js
src/hooks/useProjectStore.js
+12
-4
api.js
src/utils/api.js
+2
-2
No files found.
docs/半实物仿真方案.md
deleted
100644 → 0
View file @
abe22eec
This diff is collapsed.
Click to expand it.
docs/接口文档.md
View file @
6f2731f4
...
@@ -84,9 +84,17 @@
...
@@ -84,9 +84,17 @@
**请求:**
**请求:**
```
json
```
json
{
"model_name"
:
"Circuit"
}
{
"model_name"
:
"Circuit"
,
"stop_time"
:
2.0
}
```
```
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| model_name | string | ✅ | 模型名称 |
| stop_time | number | - | 仿真终止时间(秒),默认 1.0 |
**成功响应**
(
`code: 0`
):
**成功响应**
(
`code: 0`
):
```
json
```
json
{
{
...
@@ -138,6 +146,7 @@
...
@@ -138,6 +146,7 @@
"sim_mode"
:
"switch"
,
"sim_mode"
:
"switch"
,
"ports"
:
[
"p"
,
"n"
],
"ports"
:
[
"p"
,
"n"
],
"fmu_var_prefix"
:
"idealSwitch_1"
,
"fmu_var_prefix"
:
"idealSwitch_1"
,
"control_var"
:
"idealSwitch_1.closed"
,
"topic"
:
"/Circuit_1/idealSwitch_1"
,
"topic"
:
"/Circuit_1/idealSwitch_1"
,
"hw_label"
:
"开关"
,
"hw_label"
:
"开关"
,
"hw_node_id"
:
"new-device-3"
"hw_node_id"
:
"new-device-3"
...
@@ -162,6 +171,7 @@
...
@@ -162,6 +171,7 @@
| hardware_ports
[]
.topic | string | - | ROS2 话题前缀 |
| hardware_ports
[]
.topic | string | - | ROS2 话题前缀 |
| hardware_ports
[]
.hw_label | string | - | 节点显示名称 |
| hardware_ports
[]
.hw_label | string | - | 节点显示名称 |
| hardware_ports
[]
.hw_node_id | string | - | 前端节点 ID |
| hardware_ports
[]
.hw_node_id | string | - | 前端节点 ID |
| hardware_ports
[]
.control_var | string | - | 控制变量全名,如
`idealSwitch_1.closed`
|
| step_size | number | - | 仿真步长(秒),默认 0.001 |
| step_size | number | - | 仿真步长(秒),默认 0.001 |
| duration | number | - | 仿真时长(秒),默认 10.0 |
| duration | number | - | 仿真时长(秒),默认 10.0 |
...
...
src/components/ProjectPanel/ProjectPanel.jsx
View file @
6f2731f4
...
@@ -54,6 +54,7 @@ export default function ProjectPanel() {
...
@@ -54,6 +54,7 @@ export default function ProjectPanel() {
const
[
showCompileLog
,
setShowCompileLog
]
=
useState
(
false
);
const
[
showCompileLog
,
setShowCompileLog
]
=
useState
(
false
);
const
[
showExecuteLog
,
setShowExecuteLog
]
=
useState
(
false
);
const
[
showExecuteLog
,
setShowExecuteLog
]
=
useState
(
false
);
const
[
showResultsMode
,
setShowResultsMode
]
=
useState
(
null
);
// null | 'sim' | 'hil'
const
[
showResultsMode
,
setShowResultsMode
]
=
useState
(
null
);
// null | 'sim' | 'hil'
const
[
simDuration
,
setSimDuration
]
=
useState
(
1
);
const
[
hilDuration
,
setHilDuration
]
=
useState
(
10
);
const
[
hilDuration
,
setHilDuration
]
=
useState
(
10
);
const
activeProject
=
projects
.
find
(
p
=>
p
.
id
===
activeProjectId
)
||
null
;
const
activeProject
=
projects
.
find
(
p
=>
p
.
id
===
activeProjectId
)
||
null
;
...
@@ -228,12 +229,12 @@ export default function ProjectPanel() {
...
@@ -228,12 +229,12 @@ export default function ProjectPanel() {
/>
/>
</
div
>
</
div
>
{
/* 操作按钮 */
}
{
/* 编译按钮 */
}
<
div
className=
{
styles
.
simBtnRow
}
>
<
button
<
button
className=
{
`${styles.simBtn} ${styles.compile}`
}
className=
{
`${styles.simBtn} ${styles.compile}`
}
onClick=
{
(
e
)
=>
{
e
.
stopPropagation
();
compileProject
(
p
.
id
);
}
}
onClick=
{
(
e
)
=>
{
e
.
stopPropagation
();
compileProject
(
p
.
id
);
}
}
disabled=
{
p
.
compileStatus
===
'compiling'
}
disabled=
{
p
.
compileStatus
===
'compiling'
}
style=
{
{
width
:
'100%'
,
marginBottom
:
6
}
}
>
>
{
p
.
compileStatus
===
'compiling'
?
(
{
p
.
compileStatus
===
'compiling'
?
(
<><
span
className=
{
styles
.
spinner
}
>
⟳
</
span
>
编译中…
</>
<><
span
className=
{
styles
.
spinner
}
>
⟳
</
span
>
编译中…
</>
...
@@ -241,11 +242,33 @@ export default function ProjectPanel() {
...
@@ -241,11 +242,33 @@ export default function ProjectPanel() {
<>
▶ 编译模型
</>
<>
▶ 编译模型
</>
)
}
)
}
</
button
>
</
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
<
button
className=
{
`${styles.simBtn} ${styles.execute}`
}
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'
}
disabled=
{
p
.
compileStatus
!==
'success'
||
p
.
executeStatus
===
'running'
}
title=
{
p
.
compileStatus
!==
'success'
?
'请先编译成功'
:
'下发等效设备执行'
}
title=
{
p
.
compileStatus
!==
'success'
?
'请先编译成功'
:
'下发等效设备执行'
}
style=
{
{
width
:
'100%'
,
marginBottom
:
6
}
}
>
>
{
p
.
executeStatus
===
'running'
?
(
{
p
.
executeStatus
===
'running'
?
(
<><
span
className=
{
styles
.
spinner
}
>
⟳
</
span
>
下发中…
</>
<><
span
className=
{
styles
.
spinner
}
>
⟳
</
span
>
下发中…
</>
...
@@ -253,27 +276,28 @@ export default function ProjectPanel() {
...
@@ -253,27 +276,28 @@ export default function ProjectPanel() {
<>
⏵ 下发等效设备执行
</>
<>
⏵ 下发等效设备执行
</>
)
}
)
}
</
button
>
</
button
>
</
div
>
{
/* 半实物仿真按钮 */
}
{
/* 半实物仿真
: 时长 + HIL
按钮 */
}
{
(()
=>
{
{
(()
=>
{
const
hwCount
=
(
p
.
nodes
||
[]).
filter
(
n
=>
n
.
data
?.
isHardware
).
length
;
const
hwCount
=
(
p
.
nodes
||
[]).
filter
(
n
=>
n
.
data
?.
isHardware
).
length
;
const
hilDisabled
=
hwCount
===
0
||
p
.
compileStatus
!==
'success'
||
p
.
hilStatus
===
'starting'
||
p
.
hilStatus
===
'running'
;
return
(
return
(
<>
<>
{
/* 仿真时长输入 */
}
{
hwCount
>
0
&&
(
{
hwCount
>
0
&&
p
.
hilStatus
!==
'running'
&&
(
<
div
style=
{
{
display
:
'flex'
,
alignItems
:
'center'
,
gap
:
6
,
marginBottom
:
4
}
}
>
<
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
<
input
type=
"number"
type=
"number"
min=
"1"
max=
"3600"
step=
"1"
min=
"1"
max=
"3600"
step=
"1"
value=
{
hilDuration
}
value=
{
hilDuration
}
onChange=
{
(
e
)
=>
setHilDuration
(
Number
(
e
.
target
.
value
)
||
10
)
}
onChange=
{
(
e
)
=>
setHilDuration
(
Number
(
e
.
target
.
value
)
||
10
)
}
onClick=
{
(
e
)
=>
e
.
stopPropagation
()
}
onClick=
{
(
e
)
=>
e
.
stopPropagation
()
}
disabled=
{
hilDisabled
}
style=
{
{
style=
{
{
flex
:
1
,
padding
:
'3px 6px'
,
fontSize
:
11
,
flex
:
1
,
padding
:
'3px 6px'
,
fontSize
:
11
,
background
:
'rgba(255,255,255,0.06)'
,
border
:
'1px solid rgba(255,255,255,0.12)'
,
background
:
'rgba(255,255,255,0.06)'
,
border
:
'1px solid rgba(255,255,255,0.12)'
,
borderRadius
:
4
,
color
:
'#ccc'
,
outline
:
'none'
,
borderRadius
:
4
,
color
:
'#ccc'
,
outline
:
'none'
,
opacity
:
hilDisabled
?
0.4
:
1
,
}
}
}
}
/>
/>
<
span
style=
{
{
fontSize
:
11
,
color
:
'#666'
}
}
>
秒
</
span
>
<
span
style=
{
{
fontSize
:
11
,
color
:
'#666'
}
}
>
秒
</
span
>
...
@@ -287,8 +311,8 @@ export default function ProjectPanel() {
...
@@ -287,8 +311,8 @@ export default function ProjectPanel() {
borderColor
:
hwCount
>
0
?
'rgba(251,146,60,0.3)'
:
undefined
,
borderColor
:
hwCount
>
0
?
'rgba(251,146,60,0.3)'
:
undefined
,
color
:
hwCount
>
0
?
'#fb923c'
:
undefined
,
color
:
hwCount
>
0
?
'#fb923c'
:
undefined
,
}
}
}
}
disabled=
{
h
wCount
===
0
||
p
.
hilStatus
===
'starting'
||
p
.
hilStatus
===
'running'
}
disabled=
{
h
ilDisabled
}
title=
{
hwCount
===
0
?
'请先在属性面板中将节点标记为实物'
:
`${hwCount} 个实物节点`
}
title=
{
p
.
compileStatus
!==
'success'
?
'请先编译成功'
:
hwCount
===
0
?
'请先在属性面板中将节点标记为实物'
:
`${hwCount} 个实物节点`
}
onClick=
{
(
e
)
=>
{
e
.
stopPropagation
();
startHil
(
p
.
id
,
{
duration
:
hilDuration
});
}
}
onClick=
{
(
e
)
=>
{
e
.
stopPropagation
();
startHil
(
p
.
id
,
{
duration
:
hilDuration
});
}
}
>
>
{
p
.
hilStatus
===
'starting'
?
(
{
p
.
hilStatus
===
'starting'
?
(
...
...
src/hooks/useProjectStore.js
View file @
6f2731f4
...
@@ -53,9 +53,18 @@ const useProjectStore = create((set, get) => ({
...
@@ -53,9 +53,18 @@ const useProjectStore = create((set, get) => ({
if
(
activeProjectId
)
{
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
=>
const
updated
=
projects
.
map
(
p
=>
p
.
id
===
activeProjectId
p
.
id
===
activeProjectId
?
{
...
p
,
nodes
,
edges
,
updatedAt
:
now
,
name
:
name
||
p
.
name
}
?
{
...
p
,
nodes
,
edges
,
updatedAt
:
now
,
name
:
name
||
p
.
name
,
...
resetFields
}
:
p
:
p
);
);
persistProjects
(
updated
);
persistProjects
(
updated
);
...
@@ -211,7 +220,7 @@ const useProjectStore = create((set, get) => ({
...
@@ -211,7 +220,7 @@ const useProjectStore = create((set, get) => ({
},
},
/** 执行仿真 — 调用后端 API(仅编译成功后可执行) */
/** 执行仿真 — 调用后端 API(仅编译成功后可执行) */
executeProject
:
async
(
id
)
=>
{
executeProject
:
async
(
id
,
{
stopTime
=
1.0
}
=
{}
)
=>
{
const
{
projects
}
=
get
();
const
{
projects
}
=
get
();
const
project
=
projects
.
find
(
p
=>
p
.
id
===
id
);
const
project
=
projects
.
find
(
p
=>
p
.
id
===
id
);
if
(
!
project
||
project
.
compileStatus
!==
'success'
)
return
;
if
(
!
project
||
project
.
compileStatus
!==
'success'
)
return
;
...
@@ -228,7 +237,7 @@ const useProjectStore = create((set, get) => ({
...
@@ -228,7 +237,7 @@ const useProjectStore = create((set, get) => ({
try
{
try
{
// 响应格式: { code, message, data: { sim_log, csv_data } }
// 响应格式: { 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
;
const
isOk
=
resp
.
code
===
0
;
setStatus
({
setStatus
({
executeStatus
:
isOk
?
'success'
:
'error'
,
executeStatus
:
isOk
?
'success'
:
'error'
,
...
@@ -251,7 +260,6 @@ const useProjectStore = create((set, get) => ({
...
@@ -251,7 +260,6 @@ const useProjectStore = create((set, get) => ({
/** 启动半实物仿真 */
/** 启动半实物仿真 */
startHil
:
async
(
id
,
{
duration
=
10
,
stepSize
=
0.001
}
=
{})
=>
{
startHil
:
async
(
id
,
{
duration
=
10
,
stepSize
=
0.001
}
=
{})
=>
{
get
().
saveProject
();
const
{
projects
}
=
get
();
const
{
projects
}
=
get
();
const
project
=
projects
.
find
(
p
=>
p
.
id
===
id
);
const
project
=
projects
.
find
(
p
=>
p
.
id
===
id
);
...
...
src/utils/api.js
View file @
6f2731f4
...
@@ -39,10 +39,10 @@ export async function compileModel(moCode, modelName) {
...
@@ -39,10 +39,10 @@ export async function compileModel(moCode, modelName) {
* @param {string} modelName - 已编译的模型名称
* @param {string} modelName - 已编译的模型名称
* @returns {Promise<{code, message, data: {sim_log, model_name, csv_data?}}>}
* @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'
,
{
return
request
(
'/execute'
,
{
method
:
'POST'
,
method
:
'POST'
,
body
:
JSON
.
stringify
({
model_name
:
modelName
}),
body
:
JSON
.
stringify
({
model_name
:
modelName
,
stop_time
:
stopTime
}),
});
});
}
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment