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
231f4574
Commit
231f4574
authored
Mar 16, 2026
by
Cloud
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Merge branch 'master' into current and resolve conflicts in modelicaExporter.js
parent
2d8936ef
Changes
6
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
110 additions
and
11 deletions
+110
-11
SKILL.md
.agent/skills/eplan-visualizer/SKILL.md
+1
-0
App.jsx
src/App.jsx
+3
-0
Toolbar.jsx
src/components/Toolbar/Toolbar.jsx
+38
-3
Toolbar.module.css
src/components/Toolbar/Toolbar.module.css
+22
-0
modelicaExporter.js
src/utils/modelicaExporter.js
+38
-8
vite.config.js
vite.config.js
+8
-0
No files found.
.agent/skills/eplan-visualizer/SKILL.md
View file @
231f4574
...
...
@@ -74,6 +74,7 @@ npm run build
|
`data-import`
| xlsx 导入、JSON 导入导出、自动布局 |
|
`ui-styling`
| 暗色主题、CSS Modules、工具栏/侧栏/属性面板 |
|
`runtime-setup`
| nvm 环境搭建、项目启动、端口转发 |
|
`backend-integration`
| 后端项目路径、接口文档位置 |
## 建模理念
...
...
src/App.jsx
View file @
231f4574
/**
* App - 主入口组件
* 支持两个视图:流程编辑器 / 符号编辑器
* 右侧集成项目管理面板
*/
import
{
ReactFlowProvider
}
from
'@xyflow/react'
;
import
Toolbar
from
'./components/Toolbar/Toolbar'
;
...
...
@@ -8,6 +9,7 @@ import Sidebar from './components/Sidebar/Sidebar';
import
FlowCanvas
from
'./components/Canvas/FlowCanvas'
;
import
PropertiesPanel
from
'./components/PropertiesPanel/PropertiesPanel'
;
import
ComponentEditor
from
'./components/ComponentEditor/ComponentEditor'
;
import
ProjectPanel
from
'./components/ProjectPanel/ProjectPanel'
;
import
useComponentLibrary
from
'./hooks/useComponentLibrary'
;
import
'./App.css'
;
...
...
@@ -23,6 +25,7 @@ export default function App() {
<
Sidebar
/>
<
FlowCanvas
/>
<
PropertiesPanel
/>
<
ProjectPanel
/>
</
div
>
)
:
(
<
ComponentEditor
/>
...
...
src/components/Toolbar/Toolbar.jsx
View file @
231f4574
/**
* Toolbar - 顶部工具栏
* 提供布局切换、导入导出、清空等操作
* 提供布局切换、导入导出、清空
、项目管理
等操作
*/
import
{
useCallback
,
useRef
}
from
'react'
;
import
useFlowStore
from
'../../hooks/useFlowStore'
;
import
useProjectStore
from
'../../hooks/useProjectStore'
;
import
{
downloadModelicaFile
}
from
'../../utils/modelicaExporter'
;
import
styles
from
'./Toolbar.module.css'
;
import
useComponentLibrary
from
'../../hooks/useComponentLibrary'
;
...
...
@@ -21,6 +22,17 @@ export default function Toolbar() {
nodes
,
}
=
useFlowStore
();
const
{
panelOpen
,
togglePanel
,
saveProject
,
projects
,
activeProjectId
}
=
useProjectStore
();
const
activeProject
=
useProjectStore
(
s
=>
s
.
projects
.
find
(
p
=>
p
.
id
===
s
.
activeProjectId
));
const
handleSave
=
useCallback
(()
=>
{
if
(
activeProjectId
)
{
saveProject
();
}
else
{
const
name
=
prompt
(
'请输入原理图名称'
,
`原理图
${
projects
.
length
+
1
}
`
);
if
(
name
)
saveProject
(
name
);
}
},
[
activeProjectId
,
saveProject
,
projects
.
length
]);
const
handleExportJSON
=
useCallback
(()
=>
{
const
json
=
exportToJSON
();
...
...
@@ -43,7 +55,7 @@ export default function Toolbar() {
if
(
!
name
)
return
;
const
{
errors
,
warnings
}
=
downloadModelicaFile
({
nodes
,
edges
},
name
);
if
(
errors
&&
errors
.
length
>
0
)
{
alert
(
`导出失败,以下符号未建立模型映射:\n\n`
+
errors
.
join
(
'
\
n'
)
+
'
\
n
\
n请先在侧边栏
“模型映射”
中配置对应关系。'
);
alert
(
`导出失败,以下符号未建立模型映射:\n\n`
+
errors
.
join
(
'
\
n'
)
+
'
\
n
\
n请先在侧边栏
"模型映射"
中配置对应关系。'
);
return
;
}
if
(
warnings
.
length
>
0
)
{
...
...
@@ -81,9 +93,17 @@ export default function Toolbar() {
<
div
className=
{
styles
.
toolbar
}
>
<
div
className=
{
styles
.
brand
}
>
<
span
className=
{
styles
.
logo
}
>
⚛
</
span
>
<
span
className=
{
styles
.
title
}
>
SimuFlow
</
span
>
<
span
className=
{
styles
.
title
}
>
仿真系统
</
span
>
</
div
>
{
/* 当前项目名 */
}
{
activeProject
&&
(
<>
<
div
className=
{
styles
.
divider
}
/>
<
span
className=
{
styles
.
projectName
}
>
{
activeProject
.
name
}
</
span
>
</>
)
}
<
div
className=
{
styles
.
divider
}
/>
<
div
className=
{
styles
.
group
}
>
...
...
@@ -101,6 +121,21 @@ export default function Toolbar() {
<
div
className=
{
styles
.
spacer
}
/>
<
div
className=
{
styles
.
group
}
>
<
button
className=
{
`${styles.btn} ${styles.save}`
}
onClick=
{
handleSave
}
title=
"保存原理图 (Ctrl+S)"
>
💾 保存
</
button
>
<
button
className=
{
`${styles.btn} ${panelOpen ? styles.active : ''}`
}
onClick=
{
togglePanel
}
title=
"打开/关闭项目管理面板"
>
📁 项目
</
button
>
</
div
>
<
div
className=
{
styles
.
divider
}
/>
<
div
className=
{
styles
.
group
}
>
<
button
className=
{
styles
.
btn
}
onClick=
{
handleExportJSON
}
title=
"导出 JSON 文件"
>
↧
JSON
...
...
src/components/Toolbar/Toolbar.module.css
View file @
231f4574
...
...
@@ -94,3 +94,25 @@
border-color
:
#f44336
;
color
:
#f44336
;
}
.btn.save
{
background
:
#1a2a1a
;
border-color
:
#2a5a2a
;
color
:
#6ec66e
;
}
.btn.save
:hover:not
(
:disabled
)
{
background
:
#1e3a1e
;
border-color
:
#4a8a4a
;
color
:
#8afe8a
;
}
.projectName
{
font-size
:
12px
;
color
:
#8888f0
;
font-weight
:
600
;
max-width
:
180px
;
white-space
:
nowrap
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
}
src/utils/modelicaExporter.js
View file @
231f4574
...
...
@@ -67,6 +67,15 @@ export function exportToModelica(data, modelName = 'Circuit') {
mappingOverrides
=
JSON
.
parse
(
localStorage
.
getItem
(
'eplan_model_mapping_overrides'
)
||
'{}'
);
}
catch
{
/* ignore */
}
const
typeCounters
=
{};
// 每种类型的计数器
const
usedModelNames
=
new
Set
();
// 收集使用到的完整模型名(用于 import)
/** 将 type 转为 camelCase 实例名前缀: voltage_source → voltageSource */
function
typeToCamelCase
(
type
)
{
if
(
!
type
)
return
'comp'
;
return
type
.
replace
(
/
[
-_
]([
a-zA-Z
])
/g
,
(
_
,
c
)
=>
c
.
toUpperCase
());
}
lines
.
push
(
`model
${
toMoId
(
modelName
)}
`
);
lines
.
push
(
''
);
...
...
@@ -78,8 +87,11 @@ export function exportToModelica(data, modelName = 'Circuit') {
}
});
const
usedPackages
=
new
Set
();
// 占位 — 后续回填 import 语句
const
importInsertIdx
=
lines
.
length
;
// ===== 1. 组件声明(跳过流程节点) =====
const
declLines
=
[];
nodes
.
forEach
((
node
,
idx
)
=>
{
const
td
=
node
.
data
?.
templateData
;
const
type
=
td
?.
type
;
...
...
@@ -113,11 +125,9 @@ export function exportToModelica(data, modelName = 'Circuit') {
if
(
mapping
?.
isFlowNode
)
return
;
if
(
!
mapping
)
{
// 检查用户自定义映射(自定义符号用 custom_${id} 作为 key)
const
customKey
=
td
?.
id
?
`custom_
${
td
.
id
}
`
:
type
;
const
customOverride
=
customKey
?
mappingOverrides
[
customKey
]
:
null
;
if
(
customOverride
&&
customOverride
.
modelName
)
{
// 有自定义映射:使用用户配置的模型名
const
paramParts
=
[];
(
td
?.
params
||
[]).
forEach
(
p
=>
{
const
val
=
node
.
data
?.
paramValues
?.[
p
.
key
]
??
p
.
defaultValue
;
...
...
@@ -127,7 +137,8 @@ export function exportToModelica(data, modelName = 'Circuit') {
}
});
const
paramStr
=
paramParts
.
length
>
0
?
`(
${
paramParts
.
join
(
', '
)}
)`
:
''
;
lines
.
push
(
`
${
customOverride
.
modelName
}
${
instanceName
}${
paramStr
}
;`
);
usedModelNames
.
add
(
customOverride
.
modelName
);
declLines
.
push
(
`
${
customOverride
.
modelName
}
${
instanceName
}${
paramStr
}
;`
);
return
;
}
errors
.
push
(
`"
${
label
}
" (type:
${
type
||
'custom'
}
) 未建立模型映射,请先在"模型映射"中配置`
);
...
...
@@ -151,18 +162,37 @@ export function exportToModelica(data, modelName = 'Circuit') {
});
const
paramStr
=
paramParts
.
length
>
0
?
`(
${
paramParts
.
join
(
', '
)}
)`
:
''
;
// 优先使用用户覆盖的模型名
const
finalModelName
=
mappingOverrides
[
type
]?.
modelName
||
mapping
.
modelName
;
lines
.
push
(
`
${
finalModelName
}
${
instanceName
}${
paramStr
}
;`
);
usedModelNames
.
add
(
finalModelName
);
declLines
.
push
(
`
${
finalModelName
}
${
instanceName
}${
paramStr
}
;`
);
});
// 插入 import 语句(在 model 声明与组件声明之间)
if
(
usedPackages
.
size
>
0
)
{
const
importLines
=
[...
usedPackages
].
sort
().
map
(
pkg
=>
` import
${
pkg
}
.*;`
);
// 回填 import 语句(在 model 声明与组件声明之间)
// Upstream used `usedPackages` (which relied on `typeToPackage` map), we use `usedModelNames` directly.
if
(
usedModelNames
.
size
>
0
||
typeof
usedPackages
!==
'undefined'
&&
usedPackages
.
size
>
0
)
{
const
packages
=
new
Set
();
// 包含我们通过模型名解析出来的包
usedModelNames
.
forEach
(
name
=>
{
const
dot
=
name
.
indexOf
(
'.'
);
if
(
dot
>
0
)
{
packages
.
add
(
name
.
substring
(
0
,
dot
));
}
else
{
packages
.
add
(
'Modelica.Electrical.Analog.Basic'
);
// Fallback to basic electrical
}
});
// 包含上游直接指定的包 (如果上游代码在别处添加了)
if
(
typeof
usedPackages
!==
'undefined'
)
{
usedPackages
.
forEach
(
pkg
=>
packages
.
add
(
pkg
));
}
const
importLines
=
[...
packages
].
sort
().
map
(
pkg
=>
` import
${
pkg
}
.*;`
);
// lines[0] = 'model XXX', lines[1] = '', 组件声明从 lines[2] 开始
lines
.
splice
(
2
,
0
,
...
importLines
,
''
);
}
// 插入组件声明
declLines
.
forEach
(
l
=>
lines
.
push
(
l
));
lines
.
push
(
''
);
// ===== 2. 构建流程节点端口到连接源的映射 =====
...
...
vite.config.js
View file @
231f4574
...
...
@@ -4,4 +4,12 @@ import react from '@vitejs/plugin-react'
// https://vite.dev/config/
export
default
defineConfig
({
plugins
:
[
react
()],
server
:
{
proxy
:
{
'/api'
:
{
target
:
'http://localhost:8888'
,
changeOrigin
:
true
,
},
},
},
})
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