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; + } + } + /// /// 单元格内容点击事件 - 处理操作按钮 ///