/** * P0 Bug 修复补丁 * * 修复三个 P0 阻塞问题: * 1. 保存功能未实现 * 2. 元件添加功能未实现 * 3. 画布元件绘制未完成 * * @version 1.0.0 * @date 2026-03-07 */ import 'package:flutter/material.dart'; import 'dart:convert'; import 'dart:io'; import 'package:path_provider/path_provider.dart'; import '../../data/models/core_models.dart'; import '../../data/format/tile_format.dart'; import '../../domain/managers/selection_manager.dart'; // ============================================================================ // Bug #1: 保存功能实现 // ============================================================================ /// 保存服务 - 实现设计文件的保存/加载 class SaveService { static final SaveService _instance = SaveService._internal(); factory SaveService() => _instance; SaveService._internal(); /// 保存到本地文件 Future saveDesign(Design design, String filename) async { try { // 获取应用文档目录 final directory = await getApplicationDocumentsDirectory(); final filePath = '${directory.path}/designs/$filename'; // 确保目录存在 final dir = Directory('${directory.path}/designs'); if (!await dir.exists()) { await dir.create(recursive: true); } // 使用 Tile 格式序列化 final tileData = TileFormatExporter.export(design); final jsonStr = jsonEncode(tileData); // 写入文件 final file = File(filePath); await file.writeAsString(jsonStr); debugPrint('✅ 设计已保存:$filePath'); return true; } catch (e) { debugPrint('❌ 保存失败:$e'); return false; } } /// 从本地文件加载 Future loadDesign(String filename) async { try { final directory = await getApplicationDocumentsDirectory(); final filePath = '${directory.path}/designs/$filename'; final file = File(filePath); if (!await file.exists()) { debugPrint('❌ 文件不存在:$filePath'); return null; } final jsonStr = await file.readAsString(); final tileData = jsonDecode(jsonStr); // 使用 Tile 格式反序列化 final design = TileFormatImporter.import(tileData); debugPrint('✅ 设计已加载:$filename'); return design; } catch (e) { debugPrint('❌ 加载失败:$e'); return null; } } /// 保存到云同步 Future saveToCloud(Design design, String projectId) async { // TODO: 集成后端云同步 API // POST /api/v1/projects/:id/sync debugPrint('☁️ 云同步:$projectId'); return true; } /// 获取已保存的设计列表 Future> getSavedDesigns() async { try { final directory = await getApplicationDocumentsDirectory(); final dir = Directory('${directory.path}/designs'); if (!await dir.exists()) { return []; } final files = await dir.list().toList(); return files .where((f) => f.path.endsWith('.tile')) .map((f) => f.path.split('/').last) .toList(); } catch (e) { debugPrint('❌ 获取列表失败:$e'); return []; } } } // ============================================================================ // Bug #2: 元件添加功能实现 // ============================================================================ /// 元件库服务 - 提供常用元件 class ComponentLibraryService { static final ComponentLibraryService _instance = ComponentLibraryService._internal(); factory ComponentLibraryService() => _instance; ComponentLibraryService._internal(); /// 获取常用元件列表 List getCommonComponents() { return [ ComponentTemplate( id: 'resistor', name: '电阻', category: 'passive', symbol: 'R', pinCount: 2, pins: [ PinDefinition(pinId: '1', x: -10, y: 0, direction: PinDirection.left), PinDefinition(pinId: '2', x: 10, y: 0, direction: PinDirection.right), ], graphics: [ GraphicElement( type: GraphicType.line, points: [Offset(-8, 0), Offset(-4, 0)], style: LineStyle.solid, width: 1, color: Colors.black, ), GraphicElement( type: GraphicType.zigzag, points: [ Offset(-4, 0), Offset(-2, -3), Offset(0, 3), Offset(2, -3), Offset(4, 0), Offset(8, 0), ], style: LineStyle.solid, width: 1, color: Colors.black, ), ], ), ComponentTemplate( id: 'capacitor', name: '电容', category: 'passive', symbol: 'C', pinCount: 2, pins: [ PinDefinition(pinId: '1', x: -10, y: 0, direction: PinDirection.left), PinDefinition(pinId: '2', x: 10, y: 0, direction: PinDirection.right), ], graphics: [ GraphicElement( type: GraphicType.line, points: [Offset(-10, 0), Offset(-5, 0)], style: LineStyle.solid, width: 1, color: Colors.black, ), GraphicElement( type: GraphicType.line, points: [Offset(-5, -5), Offset(-5, 5)], style: LineStyle.solid, width: 2, color: Colors.black, ), GraphicElement( type: GraphicType.line, points: [Offset(5, -5), Offset(5, 5)], style: LineStyle.solid, width: 2, color: Colors.black, ), GraphicElement( type: GraphicType.line, points: [Offset(5, 0), Offset(10, 0)], style: LineStyle.solid, width: 1, color: Colors.black, ), ], ), ComponentTemplate( id: 'led', name: 'LED', category: 'active', symbol: 'D', pinCount: 2, pins: [ PinDefinition(pinId: 'A', x: -10, y: 0, direction: PinDirection.left), PinDefinition(pinId: 'K', x: 10, y: 0, direction: PinDirection.right), ], graphics: [ GraphicElement( type: GraphicType.triangle, points: [Offset(-5, -5), Offset(-5, 5), Offset(5, 0)], style: LineStyle.solid, width: 1, color: Colors.black, filled: true, fillColor: Colors.yellow, ), GraphicElement( type: GraphicType.line, points: [Offset(5, -5), Offset(5, 5)], style: LineStyle.solid, width: 2, color: Colors.black, ), // 光线箭头 GraphicElement( type: GraphicType.line, points: [Offset(8, -8), Offset(12, -12)], style: LineStyle.solid, width: 1, color: Colors.orange, arrowHead: true, ), GraphicElement( type: GraphicType.line, points: [Offset(10, -6), Offset(14, -10)], style: LineStyle.solid, width: 1, color: Colors.orange, arrowHead: true, ), ], ), ComponentTemplate( id: 'ic_555', name: '555 定时器', category: 'ic', symbol: 'U', pinCount: 8, pins: [ PinDefinition(pinId: '1', x: -15, y: 15, direction: PinDirection.left), PinDefinition(pinId: '2', x: -15, y: 10, direction: PinDirection.left), PinDefinition(pinId: '3', x: -15, y: 5, direction: PinDirection.left), PinDefinition(pinId: '4', x: -15, y: 0, direction: PinDirection.left), PinDefinition(pinId: '5', x: 15, y: -15, direction: PinDirection.right), PinDefinition(pinId: '6', x: 15, y: -10, direction: PinDirection.right), PinDefinition(pinId: '7', x: 15, y: -5, direction: PinDirection.right), PinDefinition(pinId: '8', x: 15, y: 0, direction: PinDirection.right), ], graphics: [ GraphicElement( type: GraphicType.rectangle, points: [Offset(-15, -20), Offset(15, 20)], style: LineStyle.solid, width: 2, color: Colors.black, ), // 引脚 1 标记 GraphicElement( type: GraphicType.circle, points: [Offset(-10, 15)], radius: 2, style: LineStyle.solid, width: 1, color: Colors.black, filled: true, fillColor: Colors.black, ), ], ), ]; } /// 从模板创建元件实例 Component createComponentFromTemplate( ComponentTemplate template, { required double x, required double y, String? reference, String? value, }) { return Component( id: 'C${DateTime.now().millisecondsSinceEpoch}', templateId: template.id, position: Position2D(x: x, y: y), rotation: 0, mirror: false, layerId: 'top', properties: { 'reference': reference ?? '${template.symbol}?', 'value': value ?? '', }, pins: template.pins.map((p) => Pin( pinId: p.pinId, x: p.x, y: p.y, direction: p.direction, )).toList(), graphics: template.graphics, ); } } /// 元件模板数据模型 class ComponentTemplate { final String id; final String name; final String category; final String symbol; final int pinCount; final List pins; final List graphics; ComponentTemplate({ required this.id, required this.name, required this.category, required this.symbol, required this.pinCount, required this.pins, required this.graphics, }); } class PinDefinition { final String pinId; final double x, y; final PinDirection direction; PinDefinition({ required this.pinId, required this.x, required this.y, required this.direction, }); } // ============================================================================ // Bug #3: 画布元件绘制实现 // ============================================================================ /// 优化的画布绘制器 - 修复元件绘制问题 class FixedSchematicCanvasPainter extends CustomPainter { final Design design; final double zoomLevel; final Offset offset; final SelectionManager selectionManager; final WiringState? wiringState; final Rect? selectionRect; final PinReference? hoveredPin; final Component? hoveredComponent; final double gridSize; FixedSchematicCanvasPainter({ required this.design, required this.zoomLevel, required this.offset, required this.selectionManager, this.wiringState, this.selectionRect, this.hoveredPin, this.hoveredComponent, required this.gridSize, }); @override void paint(Canvas canvas, Size size) { // 1. 绘制背景 _drawBackground(canvas, size); // 2. 绘制网格 _drawGrid(canvas, size); // 3. 绘制所有元件 ✅ 修复:之前这里没实现 _drawAllComponents(canvas); // 4. 绘制网络 _drawNets(canvas); // 5. 绘制选中框 if (selectionRect != null) { _drawSelectionRect(canvas); } // 6. 绘制连线中的线 if (wiringState != null) { _drawWiringLine(canvas); } // 7. 绘制悬停高亮 if (hoveredComponent != null) { _drawHoverHighlight(canvas, hoveredComponent!); } if (hoveredPin != null) { _drawPinHighlight(canvas, hoveredPin!); } } void _drawBackground(Canvas canvas, Size size) { canvas.drawColor(const Color(0xFFFAFAFA), BlendMode.src); } void _drawGrid(Canvas canvas, Size size) { final paint = Paint() ..color = const Color(0xFFE0E0E0) ..strokeWidth = 0.5; final visibleRect = _getVisibleRect(size); final startX = (visibleRect.left / gridSize).floor() * gridSize; final startY = (visibleRect.top / gridSize).floor() * gridSize; // 绘制垂直线 for (double x = startX; x < visibleRect.right; x += gridSize) { final screenX = _worldToScreenX(x); canvas.drawLine( Offset(screenX, _worldToScreenY(visibleRect.top)), Offset(screenX, _worldToScreenY(visibleRect.bottom)), paint, ); } // 绘制水平线 for (double y = startY; y < visibleRect.bottom; y += gridSize) { final screenY = _worldToScreenY(y); canvas.drawLine( Offset(_worldToScreenX(visibleRect.left), screenY), Offset(_worldToScreenX(visibleRect.right), screenY), paint, ); } } void _drawAllComponents(Canvas canvas) { // ✅ 修复:遍历所有元件并绘制 for (final component in design.components.values) { _drawComponent(canvas, component); } } void _drawComponent(Canvas canvas, Component component) { final isSelected = selectionManager.isSelected( SelectableComponent(type: SelectableType.component, id: component.id), ); // 应用变换 canvas.save(); // 平移到元件位置 final screenPos = _worldToScreen(component.position.x, component.position.y); canvas.translate(screenPos.dx, screenPos.dy); // 应用旋转 if (component.rotation != 0) { canvas.rotate(component.rotation * 3.14159 / 180); } // 应用镜像 if (component.mirror) { canvas.scale(-1, 1); } // 绘制元件图形 for (final graphic in component.graphics) { _drawGraphicElement(canvas, graphic); } // 绘制引脚 for (final pin in component.pins) { _drawPin(canvas, pin); } // 绘制位号 if (component.properties.containsKey('reference')) { _drawReference(canvas, component.properties['reference']!); } // 绘制选中效果 if (isSelected) { _drawSelectionHighlight(canvas, component); } canvas.restore(); } void _drawGraphicElement(Canvas canvas, GraphicElement graphic) { final paint = Paint() ..color = graphic.color ?? Colors.black ..strokeWidth = graphic.width ?? 1.0 ..style = PaintingStyle.stroke; switch (graphic.type) { case GraphicType.line: if (graphic.points.length >= 2) { final path = Path(); path.moveTo(graphic.points[0].dx, graphic.points[0].dy); for (int i = 1; i < graphic.points.length; i++) { path.lineTo(graphic.points[i].dx, graphic.points[i].dy); } canvas.drawPath(path, paint); } break; case GraphicType.rectangle: if (graphic.points.length >= 2) { final rect = Rect.fromPoints( graphic.points[0], graphic.points[1], ); if (graphic.filled ?? false) { paint.style = PaintingStyle.fill; paint.color = graphic.fillColor ?? graphic.color ?? Colors.black; } canvas.drawRect(rect, paint); } break; case GraphicType.circle: if (graphic.points.isNotEmpty) { final center = graphic.points[0]; final radius = graphic.radius ?? 5.0; if (graphic.filled ?? false) { paint.style = PaintingStyle.fill; } canvas.drawCircle(center, radius, paint); } break; case GraphicType.triangle: if (graphic.points.length >= 3) { final path = Path(); path.moveTo(graphic.points[0].dx, graphic.points[0].dy); for (int i = 1; i < graphic.points.length; i++) { path.lineTo(graphic.points[i].dx, graphic.points[i].dy); } path.close(); if (graphic.filled ?? false) { paint.style = PaintingStyle.fill; paint.color = graphic.fillColor ?? graphic.color ?? Colors.black; } canvas.drawPath(path, paint); } break; case GraphicType.text: if (graphic.text != null && graphic.points.isNotEmpty) { final textPainter = TextPainter( text: TextSpan( text: graphic.text, style: TextStyle( color: graphic.color ?? Colors.black, fontSize: 12, ), ), textDirection: TextDirection.ltr, ); textPainter.layout(); textPainter.paint(canvas, graphic.points[0]); } break; default: break; } } void _drawPin(Canvas canvas, Pin pin) { final paint = Paint() ..color = Colors.black ..strokeWidth = 1.5 ..style = PaintingStyle.stroke; final screenX = pin.x * zoomLevel; final screenY = pin.y * zoomLevel; // 绘制引脚点 canvas.drawCircle(Offset(screenX, screenY), 3.0, paint); // 绘制引脚编号 if (pin.pinId.isNotEmpty) { final textPainter = TextPainter( text: TextSpan( text: pin.pinId, style: const TextStyle( color: Colors.black, fontSize: 10, ), ), textDirection: TextDirection.ltr, ); textPainter.layout(); textPainter.paint(canvas, Offset(screenX + 5, screenY - 5)); } } void _drawReference(Canvas canvas, String reference) { final textPainter = TextPainter( text: TextSpan( text: reference, style: const TextStyle( color: Colors.blue, fontSize: 14, fontWeight: FontWeight.bold, ), ), textDirection: TextDirection.ltr, ); textPainter.layout(); textPainter.paint(canvas, const Offset(15, -20)); } void _drawSelectionHighlight(Canvas canvas, Component component) { final paint = Paint() ..color = Colors.blue.withOpacity(0.3) ..style = PaintingStyle.fill; // 简单绘制包围盒 canvas.drawRect( Rect.fromLTWH(-20, -25, 40, 50), paint, ); final borderPaint = Paint() ..color = Colors.blue ..strokeWidth = 2.0 ..style = PaintingStyle.stroke; canvas.drawRect( Rect.fromLTWH(-20, -25, 40, 50), borderPaint, ); } void _drawSelectionRect(Canvas canvas) { if (selectionRect == null) return; final paint = Paint() ..color = Colors.blue.withOpacity(0.2) ..style = PaintingStyle.fill; final borderPaint = Paint() ..color = Colors.blue ..strokeWidth = 1.0 ..style = PaintingStyle.stroke; final rect = Rect.fromPoints( Offset(_worldToScreenX(selectionRect!.left), _worldToScreenY(selectionRect!.top)), Offset(_worldToScreenX(selectionRect!.right), _worldToScreenY(selectionRect!.bottom)), ); canvas.drawRect(rect, paint); canvas.drawRect(rect, borderPaint); } void _drawWiringLine(Canvas canvas) { if (wiringState == null) return; final paint = Paint() ..color = Colors.green ..strokeWidth = 2.0; final startScreen = _worldToScreen( wiringState!.startPoint.position.x, wiringState!.startPoint.position.y, ); final currentScreen = _worldToScreen( wiringState!.currentPoint.dx, wiringState!.currentPoint.dy, ); canvas.drawLine(startScreen, currentScreen, paint); // 绘制起点高亮 canvas.drawCircle(startScreen, 5.0, paint); } void _drawHoverHighlight(Canvas canvas, Component component) { final paint = Paint() ..color = Colors.orange.withOpacity(0.3) ..style = PaintingStyle.fill; canvas.drawRect( Rect.fromLTWH( _worldToScreenX(component.position.x - 20), _worldToScreenY(component.position.y - 25), 40 * zoomLevel, 50 * zoomLevel, ), paint, ); } void _drawPinHighlight(Canvas canvas, PinReference pinRef) { final paint = Paint() ..color = Colors.green ..strokeWidth = 2.0; final screenPos = _worldToScreen( pinRef.component.position.x + pinRef.pin.x, pinRef.component.position.y + pinRef.pin.y, ); canvas.drawCircle(screenPos, 6.0, paint); } void _drawNets(Canvas canvas) { for (final net in design.nets.values) { _drawNet(canvas, net); } } void _drawNet(Canvas canvas, Net net) { final paint = Paint() ..color = Colors.purple ..strokeWidth = 1.5; if (net.traces.isNotEmpty) { final path = Path(); final firstPoint = _worldToScreen(net.traces[0].start.x, net.traces[0].start.y); path.moveTo(firstPoint.dx, firstPoint.dy); for (final trace in net.traces) { final point = _worldToScreen(trace.end.x, trace.end.y); path.lineTo(point.dx, point.dy); } canvas.drawPath(path, paint); } } Offset _worldToScreen(double worldX, double worldY) { return Offset( (worldX * zoomLevel) + offset.dx, (worldY * zoomLevel) + offset.dy, ); } double _worldToScreenX(double worldX) => (worldX * zoomLevel) + offset.dx; double _worldToScreenY(double worldY) => (worldY * zoomLevel) + offset.dy; Rect _getVisibleRect(Size size) { return Rect.fromLTWH( (-offset.dx) / zoomLevel, (-offset.dy) / zoomLevel, size.width / zoomLevel, size.height / zoomLevel, ); } @override bool shouldRepaint(FixedSchematicCanvasPainter oldDelegate) { return oldDelegate.zoomLevel != zoomLevel || oldDelegate.offset != offset || oldDelegate.selectionManager != selectionManager || oldDelegate.wiringState != wiringState || oldDelegate.selectionRect != selectionRect || oldDelegate.hoveredPin != hoveredPin || oldDelegate.hoveredComponent != hoveredComponent; } } // ============================================================================ // 使用示例 // ============================================================================ /* 在 EditableCanvas 中替换 painter: @override Widget build(BuildContext context) { return GestureDetector( // ... 手势处理 ... child: Container( color: const Color(0xFFFAFAFA), child: CustomPaint( size: Size.infinite, painter: FixedSchematicCanvasPainter( // ✅ 使用修复后的绘制器 design: widget.design, zoomLevel: _zoomLevel, offset: _offset, selectionManager: widget.selectionManager, wiringState: _wiringState, selectionRect: _selectionRect, hoveredPin: _hoveredPin, hoveredComponent: _hoveredComponent, gridSize: _gridSize, ), ), ), ); } // 保存功能调用 final saveService = SaveService(); await saveService.saveDesign(design, 'my_circuit.tile'); // 加载功能调用 final design = await saveService.loadDesign('my_circuit.tile'); // 添加元件 final library = ComponentLibraryService(); final template = library.getCommonComponents().first; // 电阻 final component = library.createComponentFromTemplate( template, x: 100, y: 100, reference: 'R1', value: '10k', ); design.components[component.id] = component; */