WorkCamera/client/WorkCameraExport/Forms/MainForm.cs
2026-01-05 21:20:55 +08:00

465 lines
16 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using WorkCameraExport.Models;
using WorkCameraExport.Services;
namespace WorkCameraExport.Forms
{
/// <summary>
/// 主窗体 - 数据导出功能
/// </summary>
public partial class MainForm : Form
{
private readonly ApiService _apiService;
private readonly AppSettings _settings;
private CancellationTokenSource? _exportCts;
private bool _isExporting;
// 预览数据
private int _totalRecords;
private int _totalImages;
public MainForm(ApiService apiService)
{
InitializeComponent();
_apiService = apiService;
_settings = AppSettings.Load();
InitializeDefaults();
}
/// <summary>
/// 初始化默认值
/// </summary>
private void InitializeDefaults()
{
// 设置默认日期范围最近30天
dtpEndDate.Value = DateTime.Today;
dtpStartDate.Value = DateTime.Today.AddDays(-30);
// 设置默认导出路径
if (!string.IsNullOrEmpty(_settings.DefaultExportPath) &&
Directory.Exists(_settings.DefaultExportPath))
{
txtExportPath.Text = _settings.DefaultExportPath;
}
else
{
txtExportPath.Text = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
}
UpdateExportButtonState();
}
/// <summary>
/// 预览按钮点击事件
/// </summary>
private async void btnPreview_Click(object sender, EventArgs e)
{
await DoPreviewAsync();
}
/// <summary>
/// 执行预览查询
/// </summary>
private async Task DoPreviewAsync()
{
SetQueryControlsEnabled(false);
lblStatus.Text = "正在查询...";
lblStatus.ForeColor = Color.Blue;
dgvPreview.Rows.Clear();
_totalRecords = 0;
_totalImages = 0;
try
{
var query = BuildQuery();
query.PageNum = 1;
query.PageSize = 50; // 预览最多显示50条
var (success, message, data) = await _apiService.GetExportListAsync(query);
if (success && data != null)
{
_totalRecords = data.TotalNum;
// 计算图片总数(需要遍历所有记录)
_totalImages = await CalculateTotalImagesAsync(query, data.TotalNum);
// 显示预览数据
foreach (var record in data.Result)
{
dgvPreview.Rows.Add(
record.Id,
record.RecordTime?.ToString("yyyy-MM-dd HH:mm") ?? "",
record.DeptName,
TruncateContent(record.Content, 30),
record.Images.Count
);
}
// 更新统计信息
lblTotalRecords.Text = $"共 {_totalRecords} 条记录";
lblTotalImages.Text = $"约 {_totalImages} 张图片";
lblStatus.Text = $"查询完成,共 {_totalRecords} 条记录";
lblStatus.ForeColor = Color.Green;
}
else
{
ShowError(message);
}
}
catch (Exception ex)
{
ShowError($"查询异常: {ex.Message}");
}
finally
{
SetQueryControlsEnabled(true);
UpdateExportButtonState();
}
}
/// <summary>
/// 计算图片总数
/// </summary>
private async Task<int> CalculateTotalImagesAsync(WorkRecordExportQuery query, int totalRecords)
{
if (totalRecords <= 50)
{
// 如果记录数不多,直接从预览数据计算
var (success, _, data) = await _apiService.GetExportListAsync(query);
if (success && data != null)
{
return data.Result.Sum(r => r.Images.Count);
}
}
// 估算假设平均每条记录2张图片
return totalRecords * 2;
}
/// <summary>
/// 构建查询参数
/// </summary>
private WorkRecordExportQuery BuildQuery()
{
return new WorkRecordExportQuery
{
StartDate = dtpStartDate.Value.Date,
EndDate = dtpEndDate.Value.Date.AddDays(1).AddSeconds(-1),
DeptName = string.IsNullOrWhiteSpace(txtDeptName.Text) ? null : txtDeptName.Text.Trim(),
WorkerName = string.IsNullOrWhiteSpace(txtWorkerName.Text) ? null : txtWorkerName.Text.Trim(),
Content = string.IsNullOrWhiteSpace(txtContent.Text) ? null : txtContent.Text.Trim()
};
}
/// <summary>
/// 浏览导出路径
/// </summary>
private void btnBrowse_Click(object sender, EventArgs e)
{
using var dialog = new FolderBrowserDialog
{
Description = "选择导出目录",
UseDescriptionForTitle = true,
SelectedPath = txtExportPath.Text
};
if (dialog.ShowDialog() == DialogResult.OK)
{
txtExportPath.Text = dialog.SelectedPath;
_settings.DefaultExportPath = dialog.SelectedPath;
_settings.Save();
}
}
/// <summary>
/// 导出按钮点击事件
/// </summary>
private async void btnExport_Click(object sender, EventArgs e)
{
if (_isExporting)
{
// 取消导出
_exportCts?.Cancel();
return;
}
if (_totalRecords == 0)
{
MessageBox.Show("请先预览数据", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
if (string.IsNullOrWhiteSpace(txtExportPath.Text))
{
MessageBox.Show("请选择导出目录", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
if (!Directory.Exists(txtExportPath.Text))
{
MessageBox.Show("导出目录不存在", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
await DoExportAsync();
}
/// <summary>
/// 执行导出
/// </summary>
private async Task DoExportAsync()
{
_isExporting = true;
_exportCts = new CancellationTokenSource();
SetExportingState(true);
var exportPath = Path.Combine(
txtExportPath.Text,
$"工作记录导出_{DateTime.Now:yyyyMMdd_HHmmss}.xlsx");
try
{
var query = BuildQuery();
var excelService = new ExcelService();
var allRecords = new List<WorkRecordExportDto>();
var downloadedImages = new Dictionary<string, byte[]>();
// 阶段1获取所有数据
lblStatus.Text = "正在获取数据...";
progressBar.Value = 0;
int pageNum = 1;
int totalPages = (int)Math.Ceiling(_totalRecords / 50.0);
while (true)
{
if (_exportCts.Token.IsCancellationRequested)
{
lblStatus.Text = "导出已取消";
lblStatus.ForeColor = Color.Orange;
return;
}
query.PageNum = pageNum;
query.PageSize = 50;
var (success, message, data) = await _apiService.GetExportListAsync(query);
if (!success || data == null)
{
throw new Exception($"获取数据失败: {message}");
}
allRecords.AddRange(data.Result);
// 更新进度
int progress = (int)((pageNum * 100.0) / totalPages * 0.3); // 数据获取占30%
progressBar.Value = Math.Min(progress, 30);
lblProgress.Text = $"获取数据: {pageNum}/{totalPages} 页";
if (data.Result.Count < 50 || pageNum >= data.TotalPage)
break;
pageNum++;
}
// 阶段2下载图片
lblStatus.Text = "正在下载图片...";
var allImageUrls = allRecords.SelectMany(r => r.Images).Distinct().ToList();
int downloadedCount = 0;
int totalImages = allImageUrls.Count;
// 使用信号量限制并发数为5
using var semaphore = new SemaphoreSlim(5);
var downloadTasks = allImageUrls.Select(async url =>
{
await semaphore.WaitAsync(_exportCts.Token);
try
{
if (_exportCts.Token.IsCancellationRequested)
return;
var (success, imageData, _) = await _apiService.DownloadImageAsync(url, _exportCts.Token);
if (success && imageData != null)
{
lock (downloadedImages)
{
downloadedImages[url] = imageData;
}
}
Interlocked.Increment(ref downloadedCount);
// 更新进度在UI线程
this.Invoke(() =>
{
int progress = 30 + (int)((downloadedCount * 100.0) / totalImages * 0.5); // 图片下载占50%
progressBar.Value = Math.Min(progress, 80);
lblProgress.Text = $"下载图片: {downloadedCount}/{totalImages}";
});
}
finally
{
semaphore.Release();
}
});
await Task.WhenAll(downloadTasks);
if (_exportCts.Token.IsCancellationRequested)
{
lblStatus.Text = "导出已取消";
lblStatus.ForeColor = Color.Orange;
return;
}
// 阶段3生成Excel
lblStatus.Text = "正在生成Excel...";
progressBar.Value = 80;
lblProgress.Text = "生成Excel文件...";
await Task.Run(() =>
{
excelService.GenerateExcel(exportPath, allRecords, downloadedImages,
(current, total) =>
{
this.Invoke(() =>
{
int progress = 80 + (int)((current * 100.0) / total * 0.2); // Excel生成占20%
progressBar.Value = Math.Min(progress, 100);
lblProgress.Text = $"生成Excel: {current}/{total}";
});
});
}, _exportCts.Token);
// 完成
progressBar.Value = 100;
lblProgress.Text = "导出完成";
lblStatus.Text = $"导出完成: {exportPath}";
lblStatus.ForeColor = Color.Green;
// 打开文件所在目录
if (MessageBox.Show("导出完成!是否打开文件所在目录?", "提示",
MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
{
System.Diagnostics.Process.Start("explorer.exe", $"/select,\"{exportPath}\"");
}
}
catch (OperationCanceledException)
{
lblStatus.Text = "导出已取消";
lblStatus.ForeColor = Color.Orange;
}
catch (Exception ex)
{
ShowError($"导出失败: {ex.Message}");
}
finally
{
_isExporting = false;
_exportCts?.Dispose();
_exportCts = null;
SetExportingState(false);
}
}
/// <summary>
/// 设置查询控件启用状态
/// </summary>
private void SetQueryControlsEnabled(bool enabled)
{
dtpStartDate.Enabled = enabled;
dtpEndDate.Enabled = enabled;
txtDeptName.Enabled = enabled;
txtWorkerName.Enabled = enabled;
txtContent.Enabled = enabled;
btnPreview.Enabled = enabled;
}
/// <summary>
/// 设置导出状态
/// </summary>
private void SetExportingState(bool exporting)
{
SetQueryControlsEnabled(!exporting);
txtExportPath.Enabled = !exporting;
btnBrowse.Enabled = !exporting;
if (exporting)
{
btnExport.Text = "取消导出";
btnExport.BackColor = Color.FromArgb(220, 53, 69);
}
else
{
btnExport.Text = "导出Excel";
btnExport.BackColor = Color.FromArgb(40, 167, 69);
progressBar.Value = 0;
lblProgress.Text = "";
}
}
/// <summary>
/// 更新导出按钮状态
/// </summary>
private void UpdateExportButtonState()
{
btnExport.Enabled = _totalRecords > 0 || _isExporting;
}
/// <summary>
/// 显示错误信息
/// </summary>
private void ShowError(string message)
{
lblStatus.Text = message;
lblStatus.ForeColor = Color.Red;
}
/// <summary>
/// 截断内容
/// </summary>
private static string TruncateContent(string content, int maxLength)
{
if (string.IsNullOrEmpty(content)) return "";
return content.Length <= maxLength ? content : content[..maxLength] + "...";
}
/// <summary>
/// 窗体关闭事件
/// </summary>
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
if (_isExporting)
{
var result = MessageBox.Show("正在导出中,确定要取消并退出吗?", "确认",
MessageBoxButtons.YesNo, MessageBoxIcon.Question);
if (result == DialogResult.No)
{
e.Cancel = true;
return;
}
_exportCts?.Cancel();
}
}
/// <summary>
/// 历史数据迁移按钮点击事件
/// </summary>
private void btnMigration_Click(object sender, EventArgs e)
{
if (_isExporting)
{
MessageBox.Show("正在导出中,请等待导出完成后再进行迁移操作", "提示",
MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
using var migrationForm = new MigrationForm(_apiService);
migrationForm.ShowDialog(this);
}
}
}