using Laservall.Solidworks.Model; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Laservall.Solidworks.Server.Handlers { internal sealed class ExportHandler { private readonly Func _getLastResult; private readonly IBomDataProvider _dataProvider; public ExportHandler(Func getLastResult, IBomDataProvider dataProvider) { _getLastResult = getLastResult; _dataProvider = dataProvider; } public async Task HandleExport(HttpListenerContext context, CancellationToken ct) { var result = _getLastResult(); if (result == null || result.Items == null || result.Items.Count == 0) { context.Response.StatusCode = 404; byte[] err = Encoding.UTF8.GetBytes("{\"error\":\"No BOM data loaded\"}"); context.Response.ContentType = "application/json; charset=utf-8"; context.Response.ContentLength64 = err.Length; await context.Response.OutputStream.WriteAsync(err, 0, err.Length, ct); context.Response.Close(); return; } bool isFlatView = context.Request.QueryString["flat"] == "true"; var items = isFlatView ? _dataProvider.GetFlatBomItems(result.Items) : result.Items; var settings = result.Settings; var csv = BuildCsv(items, result.DynamicKeys, settings, isFlatView); // UTF-8 BOM for Excel compatibility byte[] bom = new byte[] { 0xEF, 0xBB, 0xBF }; byte[] csvBytes = Encoding.UTF8.GetBytes(csv); byte[] body = new byte[bom.Length + csvBytes.Length]; Buffer.BlockCopy(bom, 0, body, 0, bom.Length); Buffer.BlockCopy(csvBytes, 0, body, bom.Length, csvBytes.Length); context.Response.StatusCode = 200; context.Response.ContentType = "text/csv; charset=utf-8"; context.Response.Headers.Set("Content-Disposition", "attachment; filename=\"BOM_Export.csv\""); context.Response.ContentLength64 = body.Length; await context.Response.OutputStream.WriteAsync(body, 0, body.Length, ct); context.Response.Close(); } private static string BuildCsv( List items, List dynamicKeys, BomSettings settings, bool isFlatView) { var builtInDefs = new List<(string Name, Func Getter)> { ("层级", item => item.LevelDisplay ?? ""), ("图号", item => item.DrawingNo ?? ""), ("零件名称", item => item.ConfigName ?? ""), ("属性", item => item.Classification ?? ""), ("材料", item => item.MaterialProp ?? ""), ("材质", item => item.Material ?? ""), ("数量", item => item.Quantity.ToString()), ("装配体", item => item.IsAssembly ? "是" : "否"), ("外购件", item => item.IsOutSourcing ? "是" : "否") }; var exportBuiltIns = builtInDefs .Where(d => settings.IsColumnExport(d.Name)) .Where(d => !isFlatView || (d.Name != "层级" && d.Name != "装配体")) .ToList(); var exportDynamicKeys = (dynamicKeys ?? new List()) .Where(k => settings.IsColumnExport(k)) .ToList(); var sb = new StringBuilder(); var headers = exportBuiltIns.Select(d => d.Name).Concat(exportDynamicKeys).ToList(); sb.AppendLine(string.Join(",", headers.Select(EscapeCsvField))); foreach (var item in items) { var fields = new List(); foreach (var def in exportBuiltIns) { fields.Add(def.Getter(item)); } foreach (var key in exportDynamicKeys) { fields.Add(item.GetProp(key)); } sb.AppendLine(string.Join(",", fields.Select(EscapeCsvField))); } return sb.ToString(); } private static string EscapeCsvField(string field) { if (string.IsNullOrEmpty(field)) return ""; if (field.Contains(",") || field.Contains("\"") || field.Contains("\n")) { return "\"" + field.Replace("\"", "\"\"") + "\""; } return field; } } }