using OrderSupportTicket.Models.Api; using OrderSupportTicket.Services; using System; using System.Globalization; using System.Threading.Tasks; using System.Windows; using System.Windows.Media; using WPFCommon.Models; namespace OrderSupportTicket.ViewModels; /// /// 留言气泡中的公开图片附件。 /// public sealed class ChatAttachmentViewModel : BindableBaseEx { private bool _isLoading; private bool _isLoadFailed; private ImageSource _image; public ChatAttachmentViewModel(TicketAttachmentDto dto) { if (dto == null) throw new ArgumentNullException(nameof(dto)); Id = dto.Id; OriginalName = dto.OriginalName; ContentType = dto.ContentType; ByteSize = dto.ByteSize; ReadUrl = dto.ReadUrl; } /// /// 附件唯一 ID。 /// public string Id { get; } /// /// 原始文件名。 /// public string OriginalName { get; } /// /// 图片 MIME 类型。 /// public string ContentType { get; } /// /// 图片字节数。 /// public long ByteSize { get; } /// /// 服务端生成的短时读取地址。 /// public string ReadUrl { get; } /// /// 已下载并解码的图片;加载完成前为空。 /// public ImageSource Image { get => _image; private set { if (SetValue(ref _image, value)) RaisePropertyChanged(nameof(HasImage)); } } /// /// 是否已成功显示图片。 /// public bool HasImage => Image != null; /// /// 图片是否已经确认加载失败。加载完成前保持为 false。 /// public bool IsLoadFailed { get => _isLoadFailed; private set { if (SetValue(ref _isLoadFailed, value)) RaisePropertyChanged(nameof(ToolTipText)); } } /// /// 附件名称、大小与加载状态提示。 /// public string ToolTipText { get { var name = string.IsNullOrWhiteSpace(OriginalName) ? "图片" : OriginalName; var size = FormatSize(ByteSize); if (string.IsNullOrWhiteSpace(ReadUrl)) return name + "(" + size + ",暂不可用)"; if (_isLoading) return name + "(" + size + ",加载中…)"; if (IsLoadFailed) return name + "(" + size + ",加载失败)"; return name + "(" + size + ")"; } } /// /// 下载并解码短时读取地址。失败时保留占位提示,不阻断文字留言展示。 /// public async Task LoadAsync() { if (Image != null || _isLoading) return; if (string.IsNullOrWhiteSpace(ReadUrl)) { IsLoadFailed = true; return; } IsLoadFailed = false; _isLoading = true; RaisePropertyChanged(nameof(ToolTipText)); try { var image = await ProductImageLoader.LoadAsync(ReadUrl); if (image == null) IsLoadFailed = true; else await SetImageAsync(image); } catch { IsLoadFailed = true; } finally { _isLoading = false; RaisePropertyChanged(nameof(ToolTipText)); } } private async Task SetImageAsync(ImageSource image) { if (image == null) return; var dispatcher = Application.Current?.Dispatcher; if (dispatcher == null || dispatcher.CheckAccess()) { Image = image; IsLoadFailed = false; return; } await dispatcher.InvokeAsync(() => { Image = image; IsLoadFailed = false; }); } private static string FormatSize(long byteSize) { if (byteSize >= 1024 * 1024) return (byteSize / 1024d / 1024d).ToString("0.0", CultureInfo.CurrentCulture) + " MB"; if (byteSize >= 1024) return (byteSize / 1024d).ToString("0.0", CultureInfo.CurrentCulture) + " KB"; return byteSize.ToString(CultureInfo.CurrentCulture) + " B"; } }