增加工单模块
This commit is contained in:
1 parent
dc136e449e
commit
f9dcae4610
17 files changed
+4908
No files matched your search
@@ -0,0 +1,135 @@
|
||||
using OrderSupportTicket.Models.Api;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using WPFCommon.Models;
|
||||
|
||||
namespace OrderSupportTicket.ViewModels;
|
||||
|
||||
/// <summary>
|
||||
/// 工单留言板中的单条消息。
|
||||
/// </summary>
|
||||
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));
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 消息唯一 ID。
|
||||
/// </summary>
|
||||
public string MessageId
|
||||
{
|
||||
get => _messageId;
|
||||
private set => SetValue(ref _messageId, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 工单内连续递增的消息序号。
|
||||
/// </summary>
|
||||
public int SequenceNo
|
||||
{
|
||||
get => _sequenceNo;
|
||||
private set => SetValue(ref _sequenceNo, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 消息发送方类型。
|
||||
/// </summary>
|
||||
public string SenderType
|
||||
{
|
||||
get => _senderType;
|
||||
private set
|
||||
{
|
||||
if (SetValue(ref _senderType, value))
|
||||
RaisePropertyChanged(nameof(IsCustomer));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 消息发送方显示名称。
|
||||
/// </summary>
|
||||
public string SenderDisplayName
|
||||
{
|
||||
get => _senderDisplayName;
|
||||
private set => SetValue(ref _senderDisplayName, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 消息展示内容;撤回消息显示固定提示文本。
|
||||
/// </summary>
|
||||
public string Content
|
||||
{
|
||||
get => _content;
|
||||
private set => SetValue(ref _content, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 消息是否已撤回。
|
||||
/// </summary>
|
||||
public bool IsRecalled
|
||||
{
|
||||
get => _isRecalled;
|
||||
private set => SetValue(ref _isRecalled, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 消息发送时间。
|
||||
/// </summary>
|
||||
public DateTimeOffset CreatedAt
|
||||
{
|
||||
get => _createdAt;
|
||||
private set
|
||||
{
|
||||
if (SetValue(ref _createdAt, value))
|
||||
RaisePropertyChanged(nameof(CreatedAtText));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前消息是否由客户发送。
|
||||
/// </summary>
|
||||
public bool IsCustomer =>
|
||||
string.Equals(SenderType, "customer", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// 适合聊天界面展示的本地时间文本。
|
||||
/// </summary>
|
||||
public string CreatedAtText =>
|
||||
CreatedAt == default
|
||||
? string.Empty
|
||||
: CreatedAt.ToLocalTime().ToString("MM-dd HH:mm", CultureInfo.CurrentCulture);
|
||||
|
||||
private static DateTimeOffset ParseTimestamp(string value)
|
||||
{
|
||||
if (DateTimeOffset.TryParse(
|
||||
value,
|
||||
CultureInfo.InvariantCulture,
|
||||
DateTimeStyles.AssumeUniversal,
|
||||
out var parsed))
|
||||
{
|
||||
return parsed;
|
||||
}
|
||||
return default;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,289 @@
|
||||
using OrderSupportTicket.Models.Api;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using WPFCommon.Models;
|
||||
|
||||
namespace OrderSupportTicket.ViewModels;
|
||||
|
||||
/// <summary>
|
||||
/// 历史工单列表中的单个工单摘要。
|
||||
/// </summary>
|
||||
public sealed class WorkOrderSummaryViewModel : BindableBaseEx
|
||||
{
|
||||
private string _appId;
|
||||
private string _wechatOrderId;
|
||||
private string _status;
|
||||
private string _statusText;
|
||||
private DateTimeOffset _updatedAt;
|
||||
private DateTimeOffset _lastMessageAt;
|
||||
private DateTimeOffset? _lastReplyAt;
|
||||
private string _lastSenderType;
|
||||
private int _unreadCount;
|
||||
private bool _hasNewMessage;
|
||||
private string _version;
|
||||
|
||||
public WorkOrderSummaryViewModel(WorkOrderSummaryDto dto)
|
||||
{
|
||||
if (dto == null)
|
||||
throw new ArgumentNullException(nameof(dto));
|
||||
Apply(dto);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 订单所属店铺 APPID。
|
||||
/// </summary>
|
||||
public string AppId
|
||||
{
|
||||
get => _appId;
|
||||
private set => SetValue(ref _appId, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信订单号,也是客户端使用的工单号。
|
||||
/// </summary>
|
||||
public string WechatOrderId
|
||||
{
|
||||
get => _wechatOrderId;
|
||||
private set => SetValue(ref _wechatOrderId, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 工单状态编码。
|
||||
/// </summary>
|
||||
public string Status
|
||||
{
|
||||
get => _status;
|
||||
private set
|
||||
{
|
||||
if (SetValue(ref _status, value))
|
||||
RaisePropertyChanged(nameof(IsCompleted));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 工单状态中文文本。
|
||||
/// </summary>
|
||||
public string StatusText
|
||||
{
|
||||
get => _statusText;
|
||||
private set => SetValue(ref _statusText, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 工单最后更新时间。
|
||||
/// </summary>
|
||||
public DateTimeOffset UpdatedAt
|
||||
{
|
||||
get => _updatedAt;
|
||||
private set
|
||||
{
|
||||
if (SetValue(ref _updatedAt, value))
|
||||
RaisePropertyChanged(nameof(UpdatedAtText));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 最近一条公开留言的时间。
|
||||
/// </summary>
|
||||
public DateTimeOffset LastMessageAt
|
||||
{
|
||||
get => _lastMessageAt;
|
||||
private set => SetValue(ref _lastMessageAt, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 最近一次客服正式回复时间。
|
||||
/// </summary>
|
||||
public DateTimeOffset? LastReplyAt
|
||||
{
|
||||
get => _lastReplyAt;
|
||||
private set => SetValue(ref _lastReplyAt, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 最近一条留言的发送方类型。
|
||||
/// </summary>
|
||||
public string LastSenderType
|
||||
{
|
||||
get => _lastSenderType;
|
||||
private set
|
||||
{
|
||||
if (SetValue(ref _lastSenderType, value))
|
||||
RaisePropertyChanged(nameof(PreviewText));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前工单的未读留言数量。
|
||||
/// </summary>
|
||||
public int UnreadCount
|
||||
{
|
||||
get => _unreadCount;
|
||||
private set => SetValue(ref _unreadCount, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 本次服务器刷新是否检测到比本地缓存更新的公开消息。
|
||||
/// </summary>
|
||||
public bool HasNewMessage
|
||||
{
|
||||
get => _hasNewMessage;
|
||||
private set
|
||||
{
|
||||
if (SetValue(ref _hasNewMessage, value))
|
||||
RaisePropertyChanged(nameof(PreviewText));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 工单版本号字符串。
|
||||
/// </summary>
|
||||
public string Version
|
||||
{
|
||||
get => _version;
|
||||
private set => SetValue(ref _version, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用于合并工单的 APPID 与订单号组合键。
|
||||
/// </summary>
|
||||
public string Key => AppId + "|" + WechatOrderId;
|
||||
|
||||
/// <summary>
|
||||
/// 工单是否已完结。
|
||||
/// </summary>
|
||||
public bool IsCompleted =>
|
||||
string.Equals(Status, "completed", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// 适合列表展示的本地时间文本。
|
||||
/// </summary>
|
||||
public string UpdatedAtText =>
|
||||
UpdatedAt == default
|
||||
? string.Empty
|
||||
: UpdatedAt.ToLocalTime().ToString("MM-dd HH:mm", CultureInfo.CurrentCulture);
|
||||
|
||||
/// <summary>
|
||||
/// 不读取留言正文时使用的最近动态摘要。
|
||||
/// </summary>
|
||||
public string PreviewText
|
||||
{
|
||||
get
|
||||
{
|
||||
if (HasNewMessage)
|
||||
return "有新消息,点击查看";
|
||||
if (string.Equals(
|
||||
LastSenderType,
|
||||
"customer",
|
||||
StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "客户最近有新留言";
|
||||
}
|
||||
if (LastReplyAt.HasValue)
|
||||
return "客服最近已回复";
|
||||
return "暂无公开留言";
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryApply(WorkOrderSummaryDto dto)
|
||||
{
|
||||
if (dto == null || !string.Equals(Key, dto.AppId + "|" + dto.WechatOrderId,
|
||||
StringComparison.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsAtLeastCurrentVersion(dto.Version))
|
||||
return false;
|
||||
Apply(dto);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ApplySendResult(string status, string version)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(status))
|
||||
{
|
||||
Status = status;
|
||||
StatusText = GetStatusText(status);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(version))
|
||||
Version = version;
|
||||
UpdatedAt = DateTimeOffset.Now;
|
||||
LastMessageAt = UpdatedAt;
|
||||
LastSenderType = "customer";
|
||||
HasNewMessage = false;
|
||||
}
|
||||
|
||||
public void MarkRead()
|
||||
{
|
||||
UnreadCount = 0;
|
||||
HasNewMessage = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 标记检测到新消息,并确保列表显示未读角标。
|
||||
/// </summary>
|
||||
public void MarkHasNewMessage()
|
||||
{
|
||||
HasNewMessage = true;
|
||||
if (UnreadCount <= 0)
|
||||
UnreadCount = 1;
|
||||
}
|
||||
|
||||
private void Apply(WorkOrderSummaryDto dto)
|
||||
{
|
||||
AppId = dto.AppId;
|
||||
WechatOrderId = dto.WechatOrderId;
|
||||
Status = dto.Status;
|
||||
StatusText = string.IsNullOrWhiteSpace(dto.StatusText)
|
||||
? GetStatusText(dto.Status)
|
||||
: dto.StatusText;
|
||||
UpdatedAt = ParseTimestamp(dto.UpdatedAt);
|
||||
LastMessageAt = ParseTimestamp(dto.LastMessageAt);
|
||||
LastReplyAt = ParseNullableTimestamp(dto.LastReplyAt);
|
||||
LastSenderType = dto.LastSenderType;
|
||||
UnreadCount = Math.Max(0, dto.UnreadCount);
|
||||
Version = dto.Version;
|
||||
}
|
||||
|
||||
private bool IsAtLeastCurrentVersion(string incomingVersion)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(incomingVersion))
|
||||
return true;
|
||||
if (!long.TryParse(incomingVersion, out var incoming))
|
||||
return true;
|
||||
if (!long.TryParse(Version, out var current))
|
||||
return true;
|
||||
return incoming >= current;
|
||||
}
|
||||
|
||||
private static DateTimeOffset ParseTimestamp(string value)
|
||||
{
|
||||
if (DateTimeOffset.TryParse(
|
||||
value,
|
||||
CultureInfo.InvariantCulture,
|
||||
DateTimeStyles.AssumeUniversal,
|
||||
out var parsed))
|
||||
{
|
||||
return parsed;
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
private static DateTimeOffset? ParseNullableTimestamp(string value)
|
||||
{
|
||||
var parsed = ParseTimestamp(value);
|
||||
return parsed == default ? (DateTimeOffset?)null : parsed;
|
||||
}
|
||||
|
||||
private static string GetStatusText(string status)
|
||||
{
|
||||
if (string.Equals(status, "waiting_reply", StringComparison.OrdinalIgnoreCase))
|
||||
return "待客服回复";
|
||||
if (string.Equals(status, "replied", StringComparison.OrdinalIgnoreCase))
|
||||
return "客服已回复";
|
||||
if (string.Equals(status, "completed", StringComparison.OrdinalIgnoreCase))
|
||||
return "已完结";
|
||||
return string.IsNullOrWhiteSpace(status) ? "状态未知" : status;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user