Commit 9b79e64e authored by Developer's avatar Developer

feat: 优化项目面板三段式布局、实物节点样式和HIL结果获取

- ProjectPanel: 重构为模型编译/软件仿真/半实物仿真三段式卡片布局
- ProjectPanel: 编译成功后才显示仿真按钮,项目点击展开/折叠
- ProjectPanel: 合并获取+查看HIL结果为单按钮,HIL时长默认10秒
- ProjectPanel: 半实物仿真按钮重命名,去除冗余状态提示
- CustomDeviceNode: 实物节点虚线框和发光使用卡片主题色
- CustomDeviceNode: 每种颜色独立动画名,避免多节点颜色冲突
- CustomDeviceNode: 实物节点选中时白色虚线边框+白色光晕
- PropertiesPanel: toggle关闭态改为蓝灰色,文字改为实物标记
- useProjectStore: localStorage持久化剔除csvData避免超限
- useProjectStore: fetchHilResults增加错误反馈,成功后清除errorDetail
- useProjectStore: openProject支持null参数实现折叠
parent e722bb2d
......@@ -44,16 +44,27 @@ function CustomDeviceNode({ data, selected }) {
background: '#1e1e2e',
borderRadius: 8,
outline: isHardware
? '2px dashed #fb923c'
? `${selected ? 3 : 2}px dashed ${selected ? '#fff' : color}`
: `2px solid ${selected ? '#fff' : color}`,
outlineOffset: -1,
boxShadow: isHardware
? '0 0 14px rgba(251,146,60,0.35)'
? (selected ? `0 0 16px rgba(255,255,255,0.4), 0 0 14px ${color}55` : `0 0 14px ${color}55`)
: selected ? `0 0 12px ${color}88` : '0 4px 12px rgba(0,0,0,0.3)',
overflow: 'visible',
transform: rotation ? `rotate(${rotation}deg)` : undefined,
fontFamily: "'Segoe UI', sans-serif",
animation: isHardware ? `hwGlow_${color.replace('#','')} 2s ease-in-out infinite` : undefined,
}}>
{/* 发光动画关键帧 — 按主题色独立命名 */}
{isHardware && (
<style>{`
@keyframes hwGlow_${color.replace('#','')} {
0%, 100% { box-shadow: 0 0 12px ${color}66; }
50% { box-shadow: 0 0 28px ${color}bb, 0 0 56px ${color}44; }
}
`}</style>
)}
{/* 彩色 Header */}
<div style={{
background: color,
......@@ -72,7 +83,14 @@ function CustomDeviceNode({ data, selected }) {
whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
}}>{name}</span>
{isHardware && (
<span style={{ fontSize: 12, flexShrink: 0 }} title="实物设备">🔧</span>
<span style={{
fontSize: 8, fontWeight: 800, color: '#fff',
background: '#fb923c',
border: '1.5px solid #fff',
padding: '1px 6px', borderRadius: 10,
letterSpacing: 1, lineHeight: '13px',
flexShrink: 0,
}}>HW</span>
)}
</div>
......
......@@ -492,3 +492,35 @@
background: rgba(34, 197, 94, 0.15);
color: #86efac;
}
/* ===== 三段式模块卡片 ===== */
.sectionCard {
background: rgba(255,255,255,0.02);
border: 1px solid #2a2a3a;
border-radius: 6px;
padding: 10px;
margin-bottom: 8px;
}
.sectionCardHil {
border: 1px solid rgba(251,146,60,0.18);
border-radius: 6px;
padding: 10px;
margin-bottom: 8px;
background: rgba(251,146,60,0.04);
}
.sectionTitle {
font-size: 11px;
font-weight: 700;
color: #999;
margin-bottom: 8px;
display: flex;
align-items: center;
gap: 5px;
letter-spacing: 0.5px;
}
.sectionTitleIcon {
font-size: 12px;
}
......@@ -104,10 +104,10 @@ export default function PropertiesPanel() {
))}
</div>
{/* 半实物仿真: 实物设备标记(流程节点不显示) */}
{/* 实物标记(流程节点不显示) */}
{!getModelMapping(selectedNode.data?.templateData?.type)?.isFlowNode && (
<>
<label className={styles.label}>半实物仿真</label>
<label className={styles.label}>实物标记</label>
<div
style={{
display: 'flex', alignItems: 'center', gap: 10,
......@@ -120,7 +120,7 @@ export default function PropertiesPanel() {
>
<div style={{
width: 36, height: 20, borderRadius: 10, position: 'relative',
background: selectedNode.data?.isHardware ? '#fb923c' : '#333',
background: selectedNode.data?.isHardware ? '#fb923c' : '#3b4a5c',
transition: 'background 0.2s',
}}>
<div style={{
......@@ -132,7 +132,7 @@ export default function PropertiesPanel() {
</div>
<span style={{
fontSize: 12, fontWeight: 600,
color: selectedNode.data?.isHardware ? '#fb923c' : '#666',
color: selectedNode.data?.isHardware ? '#fb923c' : '#8eafc5',
}}>
{selectedNode.data?.isHardware ? '🔧 实物设备' : '软件仿真'}
</span>
......
......@@ -19,9 +19,26 @@ function loadProjects() {
}
}
/** 保存项目列表到 localStorage */
/** 保存项目列表到 localStorage(剔除大体积数据避免超限) */
function persistProjects(projects) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(projects));
const lite = projects.map(p => {
const clean = { ...p };
// csvData 体积大,仅保留在内存不持久化
if (clean.executeResult) {
clean.executeResult = { ...clean.executeResult };
delete clean.executeResult.csvData;
}
if (clean.hilResult) {
clean.hilResult = { ...clean.hilResult };
delete clean.hilResult.csvData;
}
return clean;
});
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(lite));
} catch (e) {
console.warn('[persistProjects] localStorage 写入失败:', e.message);
}
}
function generateId() {
......@@ -119,6 +136,7 @@ const useProjectStore = create((set, get) => ({
/** 打开项目,加载数据到画布 */
openProject: (id) => {
if (!id) { set({ activeProjectId: null }); return; }
const project = get().projects.find(p => p.id === id);
if (!project) return;
const flowStore = useFlowStore.getState();
......@@ -297,10 +315,12 @@ const useProjectStore = create((set, get) => ({
return;
}
// 调用后端启动 HIL
// 调用后端启动 HIL(复用已有 FMU 跳过编译)
const cachedFmu = project.hilResult?.fmuPath || '';
const resp = await startHilSession({
moCode: exported.code,
modelName,
fmuPath: cachedFmu,
hardwarePorts: exported.hardwarePorts,
duration,
stepSize,
......@@ -335,6 +355,20 @@ const useProjectStore = create((set, get) => ({
const project = projects.find(p => p.id === id);
if (!project) return;
// 立即更新 UI 状态为完成
const setStatus = (updates) => {
const updated = get().projects.map(p =>
p.id === id ? { ...p, ...updates } : p
);
persistProjects(updated);
set({ projects: updated });
};
setStatus({
hilStatus: 'done',
hilResult: { ...project.hilResult, message: '仿真已完成' },
});
// 延迟 1s 等待 CSV 写入完成,然后获取结果
setTimeout(() => get().fetchHilResults(id), 1000);
},
......@@ -371,7 +405,7 @@ const useProjectStore = create((set, get) => ({
fetchHilResults: async (id) => {
const { projects } = get();
const project = projects.find(p => p.id === id);
if (!project || !project.hilResult?.sessionId) return;
if (!project) return;
const setStatus = (updates) => {
const updated = get().projects.map(p =>
......@@ -381,8 +415,18 @@ const useProjectStore = create((set, get) => ({
set({ projects: updated });
};
const sid = project.hilResult?.sessionId;
if (!sid) {
console.warn('[fetchHilResults] sessionId 缺失', project.hilResult);
setStatus({
hilResult: { ...project.hilResult, errorDetail: '无法获取结果:缺少 sessionId' },
});
return;
}
try {
const resp = await getHilResults(project.hilResult.sessionId);
const resp = await getHilResults(sid);
console.log('[fetchHilResults] resp:', resp);
if (resp.code === 0 && resp.data?.csv_data) {
setStatus({
hilStatus: 'done',
......@@ -390,11 +434,19 @@ const useProjectStore = create((set, get) => ({
...project.hilResult,
message: '仿真完成',
csvData: resp.data.csv_data,
errorDetail: null,
},
});
} else {
setStatus({
hilResult: { ...project.hilResult, errorDetail: resp.message || '结果为空,CSV 可能尚未写入' },
});
}
} catch (err) {
// 结果未就绪,静默失败
console.error('[fetchHilResults] error:', err);
setStatus({
hilResult: { ...project.hilResult, errorDetail: `获取失败: ${err.message}` },
});
}
},
......
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