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;
}
}