diff --git a/client/WorkCameraExport/Forms/WorkRecordForm.Designer.cs b/client/WorkCameraExport/Forms/WorkRecordForm.Designer.cs
index 0073bd9..8a44d32 100644
--- a/client/WorkCameraExport/Forms/WorkRecordForm.Designer.cs
+++ b/client/WorkCameraExport/Forms/WorkRecordForm.Designer.cs
@@ -30,6 +30,11 @@ namespace WorkCameraExport.Forms
{
components = new System.ComponentModel.Container();
+ // 图片右键菜单
+ contextMenuImage = new ContextMenuStrip(components);
+ menuItemDownloadImage = new ToolStripMenuItem();
+ menuItemCopyUrl = new ToolStripMenuItem();
+
// 主面板
panelMain = new Panel();
@@ -365,6 +370,7 @@ namespace WorkCameraExport.Forms
btnAdd.Text = "➕ 新增";
btnAdd.UseVisualStyleBackColor = false;
btnAdd.Click += btnAdd_Click;
+ btnAdd.Enabled = false; // 禁用新增按钮
//
// btnExportAll
@@ -471,6 +477,7 @@ namespace WorkCameraExport.Forms
dgvWorkRecords.CellClick += dgvWorkRecords_CellClick;
dgvWorkRecords.CellContentClick += dgvWorkRecords_CellContentClick;
dgvWorkRecords.CellPainting += dgvWorkRecords_CellPainting;
+ dgvWorkRecords.CellMouseClick += dgvWorkRecords_CellMouseClick;
//
// colSelect - 选择列
@@ -560,6 +567,7 @@ namespace WorkCameraExport.Forms
colOperations.Text = "编辑";
colOperations.UseColumnTextForButtonValue = true;
colOperations.Width = 70;
+ colOperations.ReadOnly = true; // 禁用编辑列
//
// panelPaging - 分页控件区域
@@ -760,6 +768,29 @@ namespace WorkCameraExport.Forms
//
// WorkRecordForm
+ //
+ // contextMenuImage
+ //
+ contextMenuImage.Items.AddRange(new ToolStripItem[] { menuItemDownloadImage, menuItemCopyUrl });
+ contextMenuImage.Name = "contextMenuImage";
+ contextMenuImage.Size = new Size(150, 48);
+
+ //
+ // menuItemDownloadImage
+ //
+ menuItemDownloadImage.Name = "menuItemDownloadImage";
+ menuItemDownloadImage.Size = new Size(149, 22);
+ menuItemDownloadImage.Text = "下载图片";
+ menuItemDownloadImage.Click += menuItemDownloadImage_Click;
+
+ //
+ // menuItemCopyUrl
+ //
+ menuItemCopyUrl.Name = "menuItemCopyUrl";
+ menuItemCopyUrl.Size = new Size(149, 22);
+ menuItemCopyUrl.Text = "复制URL";
+ menuItemCopyUrl.Click += menuItemCopyUrl_Click;
+
//
AutoScaleDimensions = new SizeF(7F, 17F);
AutoScaleMode = AutoScaleMode.Font;
@@ -782,6 +813,7 @@ namespace WorkCameraExport.Forms
panelPaging.ResumeLayout(false);
panelPaging.PerformLayout();
panelProgress.ResumeLayout(false);
+ contextMenuImage.ResumeLayout(false);
ResumeLayout(false);
}
@@ -850,5 +882,10 @@ namespace WorkCameraExport.Forms
private ProgressBar progressBar;
private Label lblProgress;
private Button btnCancelExport;
+
+ // 图片右键菜单
+ private ContextMenuStrip contextMenuImage;
+ private ToolStripMenuItem menuItemDownloadImage;
+ private ToolStripMenuItem menuItemCopyUrl;
}
}
diff --git a/client/WorkCameraExport/Forms/WorkRecordForm.cs b/client/WorkCameraExport/Forms/WorkRecordForm.cs
index 68f93a9..c5670ef 100644
--- a/client/WorkCameraExport/Forms/WorkRecordForm.cs
+++ b/client/WorkCameraExport/Forms/WorkRecordForm.cs
@@ -249,7 +249,7 @@ namespace WorkCameraExport.Forms
///
/// 单元格绘制事件 - 用于绘制图片缩略图
///
- private async void dgvWorkRecords_CellPainting(object? sender, DataGridViewCellPaintingEventArgs e)
+ private void dgvWorkRecords_CellPainting(object? sender, DataGridViewCellPaintingEventArgs e)
{
// 只处理图片列
if (e.ColumnIndex != colImages.Index || e.RowIndex < 0)
@@ -272,7 +272,6 @@ namespace WorkCameraExport.Forms
var startX = cellBounds.X + 5;
var startY = cellBounds.Y + (cellBounds.Height - ThumbnailHeight) / 2;
- // 绘制缩略图占位符
using var placeholderBrush = new SolidBrush(Color.FromArgb(230, 230, 230));
using var borderPen = new Pen(Color.FromArgb(200, 200, 200));
using var textBrush = new SolidBrush(Color.FromArgb(102, 102, 102));
@@ -283,16 +282,35 @@ namespace WorkCameraExport.Forms
var x = startX + i * (ThumbnailWidth + ThumbnailSpacing);
var rect = new Rectangle(x, startY, ThumbnailWidth, ThumbnailHeight);
- // 绘制占位符背景
- e.Graphics.FillRectangle(placeholderBrush, rect);
- e.Graphics.DrawRectangle(borderPen, rect);
+ var imageUrl = record.Images[i].ImageUrl;
- // 绘制图片序号
- var text = $"图{i + 1}";
- var textSize = e.Graphics.MeasureString(text, font);
- var textX = x + (ThumbnailWidth - textSize.Width) / 2;
- var textY = startY + (ThumbnailHeight - textSize.Height) / 2;
- e.Graphics.DrawString(text, font, textBrush, textX, textY);
+ // 检查缓存中是否有图片
+ if (_thumbnailCache.TryGetValue(imageUrl, out var cachedImage) && cachedImage != null)
+ {
+ // 绘制缓存的图片
+ e.Graphics.DrawImage(cachedImage, rect);
+ e.Graphics.DrawRectangle(borderPen, rect);
+ }
+ else
+ {
+ // 绘制占位符背景
+ e.Graphics.FillRectangle(placeholderBrush, rect);
+ e.Graphics.DrawRectangle(borderPen, rect);
+
+ // 绘制加载中文字
+ var text = "加载中";
+ var textSize = e.Graphics.MeasureString(text, font);
+ var textX = x + (ThumbnailWidth - textSize.Width) / 2;
+ var textY = startY + (ThumbnailHeight - textSize.Height) / 2;
+ e.Graphics.DrawString(text, font, textBrush, textX, textY);
+
+ // 异步加载图片(只触发一次)
+ if (!_thumbnailCache.ContainsKey(imageUrl))
+ {
+ _thumbnailCache[imageUrl] = null; // 标记为正在加载
+ _ = LoadThumbnailAsync(imageUrl, e.RowIndex);
+ }
+ }
}
// 绘制徽章(显示剩余图片数量)
@@ -316,6 +334,58 @@ namespace WorkCameraExport.Forms
e.Handled = true;
}
+ ///
+ /// 异步加载缩略图
+ ///
+ private async Task LoadThumbnailAsync(string imageUrl, int rowIndex)
+ {
+ try
+ {
+ var (success, imageData, _) = await _apiService.DownloadImageAsync(imageUrl);
+ if (success && imageData != null && imageData.Length > 0)
+ {
+ using var ms = new MemoryStream(imageData);
+ var originalImage = Image.FromStream(ms);
+
+ // 创建缩略图
+ var thumbnail = new Bitmap(ThumbnailWidth, ThumbnailHeight);
+ using (var g = Graphics.FromImage(thumbnail))
+ {
+ g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
+ g.DrawImage(originalImage, 0, 0, ThumbnailWidth, ThumbnailHeight);
+ }
+ originalImage.Dispose();
+
+ _thumbnailCache[imageUrl] = thumbnail;
+
+ // 刷新对应行
+ if (InvokeRequired)
+ {
+ Invoke(() => InvalidateRow(rowIndex));
+ }
+ else
+ {
+ InvalidateRow(rowIndex);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ _logService?.Warn($"加载缩略图失败: {imageUrl}, {ex.Message}");
+ }
+ }
+
+ ///
+ /// 刷新指定行
+ ///
+ private void InvalidateRow(int rowIndex)
+ {
+ if (rowIndex >= 0 && rowIndex < dgvWorkRecords.Rows.Count)
+ {
+ dgvWorkRecords.InvalidateRow(rowIndex);
+ }
+ }
+
///
/// 单元格点击事件 - 处理图片点击打开查看器
///
@@ -345,6 +415,128 @@ namespace WorkCameraExport.Forms
}
}
+ // 当前右键选中的图片信息
+ private string? _contextMenuImageUrl;
+ private int _contextMenuRowIndex = -1;
+
+ ///
+ /// 单元格鼠标点击事件 - 处理右键菜单
+ ///
+ private void dgvWorkRecords_CellMouseClick(object? sender, DataGridViewCellMouseEventArgs e)
+ {
+ if (e.RowIndex < 0 || e.Button != MouseButtons.Right)
+ return;
+
+ // 只处理图片列的右键
+ if (e.ColumnIndex == colImages.Index)
+ {
+ var row = dgvWorkRecords.Rows[e.RowIndex];
+ var record = row.Tag as WorkRecordDto;
+
+ if (record?.Images != null && record.Images.Count > 0)
+ {
+ // 计算点击的是哪张图片
+ var cellBounds = dgvWorkRecords.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, false);
+ var clickX = e.X;
+ var imageIndex = clickX / (ThumbnailWidth + ThumbnailSpacing);
+
+ if (imageIndex >= 0 && imageIndex < record.Images.Count)
+ {
+ _contextMenuImageUrl = record.Images[imageIndex].ImageUrl;
+ _contextMenuRowIndex = e.RowIndex;
+
+ // 显示右键菜单
+ var screenPoint = dgvWorkRecords.PointToScreen(new Point(
+ cellBounds.X + e.X,
+ cellBounds.Y + e.Y));
+ contextMenuImage.Show(screenPoint);
+ }
+ }
+ }
+ }
+
+ ///
+ /// 下载图片菜单项点击
+ ///
+ private async void menuItemDownloadImage_Click(object? sender, EventArgs e)
+ {
+ if (string.IsNullOrEmpty(_contextMenuImageUrl))
+ return;
+
+ try
+ {
+ // 创建 image 目录
+ var imageDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "image");
+ if (!Directory.Exists(imageDir))
+ {
+ Directory.CreateDirectory(imageDir);
+ }
+
+ // 生成文件名
+ var fileName = Path.GetFileName(_contextMenuImageUrl);
+ if (string.IsNullOrEmpty(fileName) || !fileName.Contains('.'))
+ {
+ fileName = $"image_{DateTime.Now:yyyyMMdd_HHmmss}.jpg";
+ }
+ var savePath = Path.Combine(imageDir, fileName);
+
+ // 确保文件名唯一
+ var counter = 1;
+ var baseName = Path.GetFileNameWithoutExtension(savePath);
+ var ext = Path.GetExtension(savePath);
+ while (File.Exists(savePath))
+ {
+ savePath = Path.Combine(imageDir, $"{baseName}_{counter}{ext}");
+ counter++;
+ }
+
+ lblStatusBar.Text = "正在下载图片...";
+ lblStatusBar.ForeColor = Color.Blue;
+
+ var (success, imageData, message) = await _apiService.DownloadImageAsync(_contextMenuImageUrl);
+
+ if (success && imageData != null)
+ {
+ await File.WriteAllBytesAsync(savePath, imageData);
+ lblStatusBar.Text = $"图片已保存: {savePath}";
+ lblStatusBar.ForeColor = Color.Green;
+ _logService?.Info($"图片下载成功: {savePath}");
+ }
+ else
+ {
+ lblStatusBar.Text = $"下载失败: {message}";
+ lblStatusBar.ForeColor = Color.Red;
+ }
+ }
+ catch (Exception ex)
+ {
+ lblStatusBar.Text = $"下载失败: {ex.Message}";
+ lblStatusBar.ForeColor = Color.Red;
+ _logService?.Error($"下载图片失败: {ex.Message}", ex);
+ }
+ }
+
+ ///
+ /// 复制URL菜单项点击
+ ///
+ private void menuItemCopyUrl_Click(object? sender, EventArgs e)
+ {
+ if (string.IsNullOrEmpty(_contextMenuImageUrl))
+ return;
+
+ try
+ {
+ Clipboard.SetText(_contextMenuImageUrl);
+ lblStatusBar.Text = "URL已复制到剪贴板";
+ lblStatusBar.ForeColor = Color.Green;
+ }
+ catch (Exception ex)
+ {
+ lblStatusBar.Text = $"复制失败: {ex.Message}";
+ lblStatusBar.ForeColor = Color.Red;
+ }
+ }
+
///
/// 单元格内容点击事件 - 处理操作按钮
///