using OrderSupportTicket.Models.Api; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.Linq; using System.Threading.Tasks; using WPFCommon.Models; namespace OrderSupportTicket.ViewModels; /// /// 工单留言板中的单条消息。 /// public sealed class ChatMessageViewModel : BindableBaseEx { private string _messageId; private int _sequenceNo; private string _senderType; private string _senderDisplayName; private string _content; private bool _isRecalled; private DateTimeOffset _createdAt; public ChatMessageViewModel(TicketMessageDto dto) { if (dto == null) throw new ArgumentNullException(nameof(dto)); Attachments = new ObservableCollection(); MessageId = dto.MessageId; SequenceNo = dto.SequenceNo; SenderType = dto.SenderType; IsRecalled = dto.Recalled; SenderDisplayName = string.IsNullOrWhiteSpace(dto.SenderName) ? (IsCustomer ? "我" : "客服") : dto.SenderName; Content = dto.Recalled ? "该消息已撤回" : (dto.Text ?? string.Empty); CreatedAt = ParseTimestamp(dto.CreatedAt); ReplaceAttachments(dto.Attachments); } /// /// 消息唯一 ID。 /// public string MessageId { get => _messageId; private set => SetValue(ref _messageId, value); } /// /// 工单内连续递增的消息序号。 /// public int SequenceNo { get => _sequenceNo; private set => SetValue(ref _sequenceNo, value); } /// /// 消息发送方类型。 /// public string SenderType { get => _senderType; private set { if (SetValue(ref _senderType, value)) RaisePropertyChanged(nameof(IsCustomer)); } } /// /// 消息发送方显示名称。 /// public string SenderDisplayName { get => _senderDisplayName; private set => SetValue(ref _senderDisplayName, value); } /// /// 消息展示内容;撤回消息显示固定提示文本。 /// public string Content { get => _content; private set { if (SetValue(ref _content, value)) RaisePropertyChanged(nameof(HasText)); } } /// /// 是否有可展示的文字内容。 /// public bool HasText => !string.IsNullOrWhiteSpace(Content); /// /// 消息是否已撤回。 /// public bool IsRecalled { get => _isRecalled; private set => SetValue(ref _isRecalled, value); } /// /// 消息发送时间。 /// public DateTimeOffset CreatedAt { get => _createdAt; private set { if (SetValue(ref _createdAt, value)) RaisePropertyChanged(nameof(CreatedAtText)); } } /// /// 消息中的公开图片附件。 /// public ObservableCollection Attachments { get; } /// /// 是否包含图片附件。 /// public bool HasAttachments => Attachments.Count > 0; /// /// 当前消息是否由客户发送。 /// public bool IsCustomer => string.Equals(SenderType, "customer", StringComparison.OrdinalIgnoreCase); /// /// 适合聊天界面展示的本地时间文本。 /// public string CreatedAtText => CreatedAt == default ? string.Empty : CreatedAt.ToLocalTime().ToString("MM-dd HH:mm", CultureInfo.CurrentCulture); /// /// 使用服务端最新数据更新消息。短时图片地址会随每次查询变化,因此需要刷新附件。 /// public void UpdateFrom(TicketMessageDto dto) { if (dto == null) return; SenderDisplayName = string.IsNullOrWhiteSpace(dto.SenderName) ? (IsCustomer ? "我" : "客服") : dto.SenderName; IsRecalled = dto.Recalled; Content = dto.Recalled ? "该消息已撤回" : (dto.Text ?? string.Empty); CreatedAt = ParseTimestamp(dto.CreatedAt); ReplaceAttachments(dto.Attachments); } private void ReplaceAttachments(IEnumerable attachments) { Attachments.Clear(); foreach (var dto in attachments ?? Enumerable.Empty()) { if (dto == null) continue; Attachments.Add(new ChatAttachmentViewModel(dto)); } RaisePropertyChanged(nameof(HasAttachments)); _ = LoadAttachmentsAsync(); } private async Task LoadAttachmentsAsync() { try { await Task.WhenAll(Attachments.Select(item => item.LoadAsync())); } catch { // 单张图片加载失败时保留占位提示,不影响文字留言显示。 } } private static DateTimeOffset ParseTimestamp(string value) { if (DateTimeOffset.TryParse( value, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var parsed)) { return parsed; } return default; } }