465 lines
16 KiB
C#
465 lines
16 KiB
C#
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);
|
||
}
|
||
}
|
||
}
|