feat: 初始化NewLife Studio项目,完成基础框架与数据管理模块
何炳宏 authored at 2026-05-26 12:09:09
3.29 KiB
NewLife.Studio
using System.Collections.ObjectModel;
using System.Text;
using System.Text.Json;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using NewLife.Studio.Core.DTOs;

namespace NewLife.Studio.Modules.DataStudio.ViewModels;

/// <summary>结果网格 ViewModel</summary>
public partial class ResultGridViewModel : ObservableObject
{
    [ObservableProperty]
    private ObservableCollection<ColumnInfo> _columns = [];

    [ObservableProperty]
    private ObservableCollection<object?[]> _rows = [];

    [ObservableProperty]
    private long _elapsedMs;

    [ObservableProperty]
    private int _rowCount;

    [ObservableProperty]
    private bool _isTruncated;

    [ObservableProperty]
    private string? _error;

    public string TruncatedWarning => IsTruncated ? "(结果已裁剪)" : "";
    public string ElapsedText => $"耗时: {ElapsedMs}ms";
    public string RowCountText => $"行数: {RowCount}";
    public bool HasError => !string.IsNullOrEmpty(Error);
    public bool HasRows => Rows.Count > 0 && Columns.Count > 0;

    public void SetResult(QueryResult result)
    {
        Columns = new ObservableCollection<ColumnInfo>(result.Columns);
        Rows = new ObservableCollection<object?[]>(result.Rows);
        ElapsedMs = result.ElapsedMs;
        RowCount = result.RowCount;
        IsTruncated = result.Truncated;
        Error = result.Error;

        OnPropertyChanged(nameof(TruncatedWarning));
        OnPropertyChanged(nameof(ElapsedText));
        OnPropertyChanged(nameof(RowCountText));
        OnPropertyChanged(nameof(HasError));
        OnPropertyChanged(nameof(HasRows));
    }

    public async Task ExportCsvAsync(string filePath)
    {
        var sb = new StringBuilder();

        // 写入 UTF-8 BOM 表头
        var columnNames = Columns.Select(c => EscapeCsvField(c.Name)).ToList();
        sb.AppendLine(string.Join(",", columnNames));

        // 写入数据行
        foreach (var row in Rows)
        {
            var fields = new List<string>();
            for (int i = 0; i < Columns.Count; i++)
            {
                var val = i < row.Length ? row[i]?.ToString() ?? "" : "";
                fields.Add(EscapeCsvField(val));
            }
            sb.AppendLine(string.Join(",", fields));
        }

        await File.WriteAllTextAsync(filePath, sb.ToString(), Encoding.UTF8);
    }

    public async Task ExportJsonAsync(string filePath)
    {
        var list = new List<Dictionary<string, object?>>();
        foreach (var row in Rows)
        {
            var dict = new Dictionary<string, object?>();
            for (int i = 0; i < Columns.Count; i++)
            {
                var val = i < row.Length ? row[i] : null;
                dict[Columns[i].Name] = val;
            }
            list.Add(dict);
        }

        var json = JsonSerializer.Serialize(list, new JsonSerializerOptions
        {
            WriteIndented = true,
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase
        });

        await File.WriteAllTextAsync(filePath, json, Encoding.UTF8);
    }

    private static string EscapeCsvField(string field)
    {
        if (field.Contains(',') || field.Contains('"') || field.Contains('\n') || field.Contains('\r'))
        {
            return $"\"{field.Replace("\"", "\"\"")}\"";
        }
        return field;
    }
}