Commit 1683fc76 authored by Developer's avatar Developer

feat: 优化工具栏按钮与项目管理面板

- 工具栏按钮重命名: 项目→已有, JSON→导出原理图, .mo→导出模型, 导入→导入原理图
- 新增「新建」按钮,直接创建空白原理图
- 项目面板改为可展开式设计,点击项目展开显示编译/执行状态
- 自动生成不重复的模型名称 (Circuit_1, Circuit_2, ...)
- 画布底部添加保存状态指示器:编辑中常驻显示,保存后淡出
- 工具栏保存按钮根据dirty状态切换显示
- 移除项目面板中的保存/另存为按钮
parent 641c56ec
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* FlowCanvas - React Flow 画布容器 * FlowCanvas - React Flow 画布容器
* 承载所有节点和边的渲染及交互 * 承载所有节点和边的渲染及交互
*/ */
import { useCallback, useRef, useEffect, useState } from 'react'; import { useCallback, useRef, useEffect, useState, useMemo } from 'react';
import { import {
ReactFlow, ReactFlow,
Controls, Controls,
...@@ -20,6 +20,7 @@ import GradientBezierEdge from '../Edges/GradientBezierEdge'; ...@@ -20,6 +20,7 @@ import GradientBezierEdge from '../Edges/GradientBezierEdge';
import CustomConnectionLine from '../Edges/CustomConnectionLine'; import CustomConnectionLine from '../Edges/CustomConnectionLine';
import useFlowStore from '../../hooks/useFlowStore'; import useFlowStore from '../../hooks/useFlowStore';
import useComponentLibrary from '../../hooks/useComponentLibrary'; import useComponentLibrary from '../../hooks/useComponentLibrary';
import useProjectStore from '../../hooks/useProjectStore';
import styles from './FlowCanvas.module.css'; import styles from './FlowCanvas.module.css';
...@@ -48,6 +49,29 @@ export default function FlowCanvas() { ...@@ -48,6 +49,29 @@ export default function FlowCanvas() {
connectionError, connectionError,
} = useFlowStore(); } = useFlowStore();
// 保存状态检测
const activeProject = useProjectStore(s => s.projects.find(p => p.id === s.activeProjectId));
const isDirty = useMemo(() => {
if (!activeProject) return nodes.length > 0;
const savedNodes = activeProject.nodes || [];
const savedEdges = activeProject.edges || [];
if (nodes.length !== savedNodes.length || edges.length !== savedEdges.length) return true;
return JSON.stringify(nodes) !== JSON.stringify(savedNodes)
|| JSON.stringify(edges) !== JSON.stringify(savedEdges);
}, [activeProject, nodes, edges]);
// 保存后短暂显示"已保存"然后淡出
const [justSaved, setJustSaved] = useState(false);
const prevDirtyRef = useRef(isDirty);
useEffect(() => {
if (prevDirtyRef.current && !isDirty) {
setJustSaved(true);
const timer = setTimeout(() => setJustSaved(false), 2000);
return () => clearTimeout(timer);
}
prevDirtyRef.current = isDirty;
}, [isDirty]);
/** 连接错误 Toast 状态 */ /** 连接错误 Toast 状态 */
const [showError, setShowError] = useState(null); const [showError, setShowError] = useState(null);
...@@ -235,6 +259,15 @@ export default function FlowCanvas() { ...@@ -235,6 +259,15 @@ export default function FlowCanvas() {
{showError} {showError}
</div> </div>
)} )}
{/* 保存状态指示器:未保存时常驻,保存后短暂显示再淡出 */}
{(isDirty || justSaved) && (
<div className={`${styles.saveStatus} ${isDirty ? styles.saveStatusDirty : styles.saveStatusFadeOut}`}>
<span className={styles.saveStatusIcon}>{isDirty ? '✏️' : '✅'}</span>
{activeProject && <span className={styles.saveStatusName}>{activeProject.name}</span>}
<span className={styles.saveStatusLabel}>{isDirty ? '编辑中' : '已保存'}</span>
</div>
)}
</div> </div>
); );
} }
......
...@@ -57,3 +57,62 @@ ...@@ -57,3 +57,62 @@
pointer-events: none; pointer-events: none;
animation: toastFadeIn 0.2s ease-out, toastShake 0.5s ease-in-out 0.2s; animation: toastFadeIn 0.2s ease-out, toastShake 0.5s ease-in-out 0.2s;
} }
/* ===== 右上角保存状态指示器 ===== */
.saveStatus {
position: absolute;
bottom: 12px;
left: 50%;
transform: translateX(-50%);
display: flex;
align-items: center;
gap: 6px;
padding: 6px 14px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
pointer-events: none;
z-index: 10;
transition: all 0.3s ease;
backdrop-filter: blur(8px);
}
.saveStatusFadeOut {
background: rgba(34, 197, 94, 0.12);
border: 1px solid rgba(34, 197, 94, 0.3);
color: #22c55e;
animation: fadeOut 2s ease-out forwards;
}
@keyframes fadeOut {
0%, 70% { opacity: 1; }
100% { opacity: 0; }
}
.saveStatusDirty {
background: rgba(251, 146, 60, 0.15);
border: 1px solid rgba(251, 146, 60, 0.4);
color: #fb923c;
animation: pulseDirty 2s ease-in-out infinite;
}
@keyframes pulseDirty {
0%, 100% { box-shadow: 0 0 0 0 rgba(251, 146, 60, 0); }
50% { box-shadow: 0 0 12px 2px rgba(251, 146, 60, 0.25); }
}
.saveStatusIcon {
font-size: 14px;
}
.saveStatusName {
max-width: 120px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.saveStatusLabel {
opacity: 0.8;
font-size: 11px;
}
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* ProjectPanel - 项目管理面板 * ProjectPanel - 项目管理面板
* 功能:保存/打开/重命名/删除多个原理图,编译/执行 Modelica 模型 * 功能:保存/打开/重命名/删除多个原理图,编译/执行 Modelica 模型
*/ */
import { useState, useCallback, useEffect } from 'react'; import { useState, useCallback, useEffect, useRef } from 'react';
import useProjectStore from '../../hooks/useProjectStore'; import useProjectStore from '../../hooks/useProjectStore';
import useFlowStore from '../../hooks/useFlowStore'; import useFlowStore from '../../hooks/useFlowStore';
import SimResultsModal from '../SimResults/SimResultsModal'; import SimResultsModal from '../SimResults/SimResultsModal';
...@@ -52,6 +52,17 @@ export default function ProjectPanel() { ...@@ -52,6 +52,17 @@ export default function ProjectPanel() {
const activeProject = projects.find(p => p.id === activeProjectId) || null; const activeProject = projects.find(p => p.id === activeProjectId) || null;
// 执行成功后自动弹出结果页面(仅在 running → success 转换时触发)
const prevExecStatusRef = useRef(activeProject?.executeStatus);
useEffect(() => {
const prev = prevExecStatusRef.current;
const cur = activeProject?.executeStatus;
prevExecStatusRef.current = cur;
if (prev === 'running' && cur === 'success' && activeProject?.executeResult?.csvData) {
setShowResults(true);
}
}, [activeProject?.executeStatus]);
// Ctrl+S 快捷键 // Ctrl+S 快捷键
useEffect(() => { useEffect(() => {
const handler = (e) => { const handler = (e) => {
...@@ -111,19 +122,12 @@ export default function ProjectPanel() { ...@@ -111,19 +122,12 @@ export default function ProjectPanel() {
<button className={styles.closeBtn} onClick={closePanel} title="关闭"></button> <button className={styles.closeBtn} onClick={closePanel} title="关闭"></button>
</div> </div>
{/* 操作栏 */}
<div className={styles.actions}>
<button className={`${styles.actionBtn} ${styles.primary}`} onClick={handleSave}>
💾 保存
</button>
<button className={styles.actionBtn} onClick={handleSaveAs}>
📄 另存为
</button>
</div>
{/* 滚动内容 */} {/* 滚动内容 */}
<div className={styles.content}> <div className={styles.content}>
{/* 项目列表 */} {/* 项目列表(可展开) */}
<div className={styles.projectList}> <div className={styles.projectList}>
{projects.length === 0 ? ( {projects.length === 0 ? (
<div className={styles.emptyState}> <div className={styles.emptyState}>
...@@ -134,163 +138,149 @@ export default function ProjectPanel() { ...@@ -134,163 +138,149 @@ export default function ProjectPanel() {
</div> </div>
</div> </div>
) : ( ) : (
projects.map(p => ( projects.map(p => {
<div const isActive = p.id === activeProjectId;
key={p.id} return (
className={`${styles.projectItem} ${p.id === activeProjectId ? styles.active : ''}`} <div key={p.id} className={`${styles.projectItem} ${isActive ? styles.active : ''}`}>
onClick={() => openProject(p.id)} {/* 项目标题行 */}
> <div className={styles.projectHeader} onClick={() => openProject(p.id)}>
<span className={styles.projectIcon}> <span className={styles.projectIcon}>
{p.compileStatus === 'success' ? '✅' : p.compileStatus === 'error' ? '❌' : '📄'} {p.compileStatus === 'success' ? '✅' : p.compileStatus === 'error' ? '❌' : '📄'}
</span> </span>
<div className={styles.projectInfo}> <div className={styles.projectInfo}>
{renamingId === p.id ? ( {renamingId === p.id ? (
<input <input
className={styles.renameInput} className={styles.renameInput}
value={renameValue} value={renameValue}
onChange={(e) => setRenameValue(e.target.value)} onChange={(e) => setRenameValue(e.target.value)}
onBlur={confirmRename} onBlur={confirmRename}
onKeyDown={(e) => { onKeyDown={(e) => {
if (e.key === 'Enter') confirmRename(); if (e.key === 'Enter') confirmRename();
if (e.key === 'Escape') setRenamingId(null); if (e.key === 'Escape') setRenamingId(null);
}} }}
autoFocus autoFocus
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
/> />
) : ( ) : (
<div className={styles.projectName}>{p.name}</div> <div className={styles.projectName}>{p.name}</div>
)} )}
<div className={styles.projectMeta}> <div className={styles.projectMeta}>
{p.nodes?.length || 0} 节点 · {formatTime(p.updatedAt)} {p.nodes?.length || 0} 节点 · {formatTime(p.updatedAt)}
</div>
</div>
<div className={styles.projectActions}>
<button
className={styles.iconBtn}
onClick={(e) => { e.stopPropagation(); startRename(p.id, p.name); }}
title="重命名"
>✏️</button>
<button
className={`${styles.iconBtn} ${styles.danger}`}
onClick={(e) => { e.stopPropagation(); handleDelete(p.id, p.name); }}
title="删除"
>🗑</button>
</div>
</div> </div>
</div>
<div className={styles.projectActions}>
<button
className={styles.iconBtn}
onClick={(e) => { e.stopPropagation(); startRename(p.id, p.name); }}
title="重命名"
>
✏️
</button>
<button
className={`${styles.iconBtn} ${styles.danger}`}
onClick={(e) => { e.stopPropagation(); handleDelete(p.id, p.name); }}
title="删除"
>
🗑
</button>
</div>
</div>
))
)}
</div>
{/* 编译/执行区域 — 仅当有活动项目时显示 */} {/* 展开区域:编译 & 执行 */}
{activeProject && ( {isActive && (
<div className={styles.simSection}> <div className={styles.expandedSection}>
<div className={styles.sectionLabel}>编译 &amp; 执行</div> {/* 状态指示 */}
<div className={styles.statusRow}>
<span style={{ fontSize: 10, color: '#666' }}>编译:</span>
<StatusBadge
status={p.compileStatus}
labels={{ none: '未编译', success: '编译成功', error: '编译失败' }}
/>
<span style={{ fontSize: 10, color: '#666', marginLeft: 8 }}>执行:</span>
<StatusBadge
status={p.executeStatus}
labels={{ none: '未执行', success: '执行完成', error: '执行失败' }}
/>
</div>
{/* 模型名称 */} {/* 模型名 */}
<div className={styles.modelNameRow}> <div className={styles.modelNameRow}>
<span style={{ fontSize: 11, color: '#666', flexShrink: 0 }}>模型名:</span> <span style={{ fontSize: 11, color: '#666', flexShrink: 0 }}>模型名:</span>
<input <input
className={styles.modelNameInput} className={styles.modelNameInput}
value={activeProject.modelName || 'Circuit'} value={p.modelName || 'Circuit'}
onChange={(e) => updateModelName(activeProject.id, e.target.value)} onChange={(e) => updateModelName(p.id, e.target.value)}
placeholder="Circuit" placeholder="Circuit"
/> onClick={(e) => e.stopPropagation()}
</div> />
</div>
{/* 编译/执行按钮 */}
<div className={styles.simBtnRow}>
<button
className={`${styles.simBtn} ${styles.compile}`}
onClick={() => compileProject(activeProject.id)}
disabled={activeProject.compileStatus === 'compiling'}
>
{activeProject.compileStatus === 'compiling' ? (
<><span className={styles.spinner}></span> 编译中…</>
) : (
<>▶ 编译</>
)}
</button>
<button
className={`${styles.simBtn} ${styles.execute}`}
onClick={() => executeProject(activeProject.id)}
disabled={
activeProject.compileStatus !== 'success' ||
activeProject.executeStatus === 'running'
}
title={activeProject.compileStatus !== 'success' ? '请先编译成功' : '执行仿真'}
>
{activeProject.executeStatus === 'running' ? (
<><span className={styles.spinner}></span> 执行中…</>
) : (
<>⏵ 执行</>
)}
</button>
</div>
{/* 查看结果按钮 — 仅执行成功且有 CSV 数据时显示 */} {/* 操作按钮 */}
{activeProject.executeStatus === 'success' && activeProject.executeResult?.csvData && ( <div className={styles.simBtnRow}>
<button <button
className={`${styles.simBtn} ${styles.viewResults}`} className={`${styles.simBtn} ${styles.compile}`}
onClick={() => setShowResults(true)} onClick={(e) => { e.stopPropagation(); compileProject(p.id); }}
style={{ width: '100%', marginBottom: 8 }} disabled={p.compileStatus === 'compiling'}
> >
📊 查看结果 {p.compileStatus === 'compiling' ? (
</button> <><span className={styles.spinner}></span> 编译中…</>
)} ) : (
<>▶ 编译模型</>
)}
</button>
<button
className={`${styles.simBtn} ${styles.execute}`}
onClick={(e) => { e.stopPropagation(); executeProject(p.id); }}
disabled={p.compileStatus !== 'success' || p.executeStatus === 'running'}
title={p.compileStatus !== 'success' ? '请先编译成功' : '下发等效设备执行'}
>
{p.executeStatus === 'running' ? (
<><span className={styles.spinner}></span> 下发中…</>
) : (
<>⏵ 下发等效设备执行</>
)}
</button>
</div>
{/* 编译状态 */} {/* 查看结果 */}
<div style={{ display: 'flex', gap: 8, marginBottom: 8 }}> {p.executeStatus === 'success' && p.executeResult?.csvData && (
<span style={{ fontSize: 10, color: '#666' }}>编译:</span> <button
<StatusBadge className={`${styles.simBtn} ${styles.viewResults}`}
status={activeProject.compileStatus} onClick={(e) => { e.stopPropagation(); setShowResults(true); }}
labels={{ none: '未编译', success: '编译成功', error: '编译失败' }} style={{ width: '100%', marginBottom: 4 }}
/> >📊 查看结果</button>
<span style={{ fontSize: 10, color: '#666', marginLeft: 8 }}>执行:</span> )}
<StatusBadge
status={activeProject.executeStatus}
labels={{ none: '未执行', success: '执行完成', error: '执行失败' }}
/>
</div>
{/* 编译日志 */} {/* 编译日志 */}
{activeProject.compileResult && ( {p.compileResult && (
<div className={styles.logSection}> <div className={styles.logSection}>
<button <button className={styles.logToggle} onClick={(e) => { e.stopPropagation(); setShowCompileLog(v => !v); }}>
className={styles.logToggle} {showCompileLog ? '▼' : '▶'} 编译输出
onClick={() => setShowCompileLog(v => !v)} </button>
> {showCompileLog && (
{showCompileLog ? '▼' : '▶'} 编译输出 <div className={`${styles.logContent} ${p.compileStatus === 'error' ? styles.logError : styles.logSuccess}`}>
</button> {p.compileResult.errors || p.compileResult.output || '无输出'}
{showCompileLog && ( </div>
<div className={`${styles.logContent} ${activeProject.compileStatus === 'error' ? styles.logError : styles.logSuccess}`}> )}
{activeProject.compileResult.errors || activeProject.compileResult.output || '无输出'} </div>
</div> )}
)}
</div>
)}
{/* 执行日志 */} {/* 执行日志 */}
{activeProject.executeResult && ( {p.executeResult && (
<div className={styles.logSection}> <div className={styles.logSection}>
<button <button className={styles.logToggle} onClick={(e) => { e.stopPropagation(); setShowExecuteLog(v => !v); }}>
className={styles.logToggle} {showExecuteLog ? '▼' : '▶'} 执行结果
onClick={() => setShowExecuteLog(v => !v)} </button>
> {showExecuteLog && (
{showExecuteLog ? '▼' : '▶'} 执行结果 <div className={`${styles.logContent} ${p.executeStatus === 'error' ? styles.logError : ''}`}>
</button> {p.executeResult.logs || p.executeResult.csvData || '无输出'}
{showExecuteLog && ( </div>
<div className={`${styles.logContent} ${activeProject.executeStatus === 'error' ? styles.logError : ''}`}> )}
{activeProject.executeResult.logs || activeProject.executeResult.csvData || '无输出'} </div>
</div> )}
)} </div>
</div> )}
)} </div>
</div> );
)} })
)}
</div>
</div> </div>
{/* 仿真结果弹窗 */} {/* 仿真结果弹窗 */}
......
...@@ -107,15 +107,13 @@ ...@@ -107,15 +107,13 @@
.projectItem { .projectItem {
display: flex; display: flex;
align-items: center; flex-direction: column;
gap: 10px;
padding: 10px 12px;
border: 1px solid #2a2a3a; border: 1px solid #2a2a3a;
border-radius: 8px; border-radius: 8px;
margin-bottom: 6px; margin-bottom: 6px;
cursor: pointer;
transition: all 0.15s ease; transition: all 0.15s ease;
background: rgba(255, 255, 255, 0.02); background: rgba(255, 255, 255, 0.02);
overflow: hidden;
} }
.projectItem:hover { .projectItem:hover {
...@@ -128,6 +126,15 @@ ...@@ -128,6 +126,15 @@
background: rgba(99, 102, 241, 0.08); background: rgba(99, 102, 241, 0.08);
} }
/* 项目标题行(可点击) */
.projectHeader {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 12px;
cursor: pointer;
}
.projectIcon { .projectIcon {
font-size: 18px; font-size: 18px;
color: #555; color: #555;
...@@ -138,6 +145,20 @@ ...@@ -138,6 +145,20 @@
color: #6366f1; color: #6366f1;
} }
/* 展开区域 */
.expandedSection {
padding: 8px 12px 12px;
border-top: 1px solid rgba(99, 102, 241, 0.15);
background: rgba(99, 102, 241, 0.03);
}
.statusRow {
display: flex;
gap: 8px;
align-items: center;
margin-bottom: 8px;
}
.projectInfo { .projectInfo {
flex: 1; flex: 1;
min-width: 0; min-width: 0;
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* Toolbar - 顶部工具栏 * Toolbar - 顶部工具栏
* 提供布局切换、导入导出、清空、项目管理等操作 * 提供布局切换、导入导出、清空、项目管理等操作
*/ */
import { useCallback, useRef } from 'react'; import { useCallback, useRef, useMemo } from 'react';
import useFlowStore from '../../hooks/useFlowStore'; import useFlowStore from '../../hooks/useFlowStore';
import useProjectStore from '../../hooks/useProjectStore'; import useProjectStore from '../../hooks/useProjectStore';
import { downloadModelicaFile } from '../../utils/modelicaExporter'; import { downloadModelicaFile } from '../../utils/modelicaExporter';
...@@ -20,11 +20,22 @@ export default function Toolbar() { ...@@ -20,11 +20,22 @@ export default function Toolbar() {
selectedNode, selectedNode,
selectedEdge, selectedEdge,
nodes, nodes,
edges,
} = useFlowStore(); } = useFlowStore();
const { panelOpen, togglePanel, saveProject, projects, activeProjectId } = useProjectStore(); const { panelOpen, togglePanel, saveProject, projects, activeProjectId } = useProjectStore();
const activeProject = useProjectStore(s => s.projects.find(p => p.id === s.activeProjectId)); const activeProject = useProjectStore(s => s.projects.find(p => p.id === s.activeProjectId));
// 检测画布是否有未保存的修改
const isDirty = useMemo(() => {
if (!activeProject) return nodes.length > 0; // 无项目时,有内容就算脏
const savedNodes = activeProject.nodes || [];
const savedEdges = activeProject.edges || [];
if (nodes.length !== savedNodes.length || edges.length !== savedEdges.length) return true;
return JSON.stringify(nodes) !== JSON.stringify(savedNodes)
|| JSON.stringify(edges) !== JSON.stringify(savedEdges);
}, [activeProject, nodes, edges]);
const handleSave = useCallback(() => { const handleSave = useCallback(() => {
if (activeProjectId) { if (activeProjectId) {
saveProject(); saveProject();
...@@ -100,7 +111,7 @@ export default function Toolbar() { ...@@ -100,7 +111,7 @@ export default function Toolbar() {
{activeProject && ( {activeProject && (
<> <>
<div className={styles.divider} /> <div className={styles.divider} />
<span className={styles.projectName}>{activeProject.name}</span> <span className={styles.projectName}>{activeProject.name}{isDirty ? ' ●' : ''}</span>
</> </>
)} )}
...@@ -122,29 +133,32 @@ export default function Toolbar() { ...@@ -122,29 +133,32 @@ export default function Toolbar() {
<div className={styles.spacer} /> <div className={styles.spacer} />
<div className={styles.group}> <div className={styles.group}>
<button className={`${styles.btn} ${styles.save}`} onClick={handleSave} title="保存原理图 (Ctrl+S)"> <button className={styles.btn} onClick={() => {
💾 保存 clearAll();
useProjectStore.getState().closeProject();
}} title="新建空白原理图">
✚ 新建
</button>
<button className={`${styles.btn} ${isDirty ? styles.save : styles.saved}`} onClick={handleSave} title="保存原理图 (Ctrl+S)">
{isDirty ? '💾 保存' : '✅ 已保存'}
</button> </button>
<button <button
className={`${styles.btn} ${panelOpen ? styles.active : ''}`} className={`${styles.btn} ${panelOpen ? styles.active : ''}`}
onClick={togglePanel} onClick={togglePanel}
title="打开/关闭项目管理面板" title="打开/关闭项目管理面板"
> >
📁 项目 📁 已有
</button> </button>
</div> </div>
<div className={styles.divider} /> <div className={styles.divider} />
<div className={styles.group}> <div className={styles.group}>
<button className={styles.btn} onClick={handleExportJSON} title="导出 JSON 文件"> <button className={styles.btn} onClick={handleExportJSON} title="导出原理图 JSON">
&#8615; JSON &#8615; 导出原理图
</button> </button>
<button className={styles.btn} onClick={handleExportMo} title="导出 OpenModelica .mo 模型"> <button className={styles.btn} onClick={handleImportJSON} title="导入原理图 JSON">
&#8615; .mo &#8613; 导入原理图
</button>
<button className={styles.btn} onClick={handleImportJSON} title="导入 JSON 文件">
&#8613; 导入
</button> </button>
<input <input
ref={jsonInputRef} ref={jsonInputRef}
...@@ -153,6 +167,9 @@ export default function Toolbar() { ...@@ -153,6 +167,9 @@ export default function Toolbar() {
style={{ display: 'none' }} style={{ display: 'none' }}
onChange={handleJSONFileChange} onChange={handleJSONFileChange}
/> />
<button className={styles.btn} onClick={handleExportMo} title="导出 Modelica 模型">
&#8615; 导出模型
</button>
<button className={`${styles.btn} ${styles.danger}`} onClick={handleClear} title="清空画布"> <button className={`${styles.btn} ${styles.danger}`} onClick={handleClear} title="清空画布">
&#9746; 清空 &#9746; 清空
</button> </button>
......
...@@ -107,6 +107,13 @@ ...@@ -107,6 +107,13 @@
color: #8afe8a; color: #8afe8a;
} }
.btn.saved {
background: #1e1e2e;
border-color: #333;
color: #666;
cursor: default;
}
.projectName { .projectName {
font-size: 12px; font-size: 12px;
color: #8888f0; color: #8888f0;
......
...@@ -28,6 +28,14 @@ function generateId() { ...@@ -28,6 +28,14 @@ function generateId() {
return Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 8); return Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 8);
} }
/** 生成不重复的模型名称 */
function nextModelName(projects) {
const existing = new Set(projects.map(p => p.modelName).filter(Boolean));
let i = 1;
while (existing.has(`Circuit_${i}`)) i++;
return `Circuit_${i}`;
}
const useProjectStore = create((set, get) => ({ const useProjectStore = create((set, get) => ({
projects: loadProjects(), projects: loadProjects(),
activeProjectId: null, activeProjectId: null,
...@@ -64,7 +72,7 @@ const useProjectStore = create((set, get) => ({ ...@@ -64,7 +72,7 @@ const useProjectStore = create((set, get) => ({
edges, edges,
createdAt: now, createdAt: now,
updatedAt: now, updatedAt: now,
modelName: 'Circuit', modelName: nextModelName(projects),
compileStatus: 'none', compileStatus: 'none',
compileResult: null, compileResult: null,
executeStatus: 'none', executeStatus: 'none',
...@@ -88,7 +96,7 @@ const useProjectStore = create((set, get) => ({ ...@@ -88,7 +96,7 @@ const useProjectStore = create((set, get) => ({
edges, edges,
createdAt: now, createdAt: now,
updatedAt: now, updatedAt: now,
modelName: 'Circuit', modelName: nextModelName(projects),
compileStatus: 'none', compileStatus: 'none',
compileResult: null, compileResult: null,
executeStatus: 'none', executeStatus: 'none',
......
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