Add order tail flows and migrate queue, message, settlement, and audit modules.
Wire deposit orders, historical shooting, and multi-travel uploads into Orders, and replace home placeholders for queue management, message center, scenic settlement, and withdrawal audit. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -0,0 +1,170 @@
|
||||
//
|
||||
// MessageCenterViewModel.swift
|
||||
// suixinkan
|
||||
//
|
||||
// Created by Codex on 2026/6/25.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Observation
|
||||
|
||||
@MainActor
|
||||
@Observable
|
||||
/// 消息中心 ViewModel,负责消息分页、筛选、已读和删除。
|
||||
final class MessageCenterViewModel {
|
||||
var messages: [MessageItem] = []
|
||||
var selectedFilter: MessageFilter = .all
|
||||
var isLoading = false
|
||||
var isLoadingMore = false
|
||||
var loadFailed = false
|
||||
var loadFailureReason: String?
|
||||
var message: String?
|
||||
|
||||
private(set) var hasMoreMessages = false
|
||||
private(set) var lastId = 0
|
||||
private let pageSize = 20
|
||||
|
||||
/// 未读消息数量。
|
||||
var unreadCount: Int {
|
||||
messages.filter { !$0.isRead }.count
|
||||
}
|
||||
|
||||
/// 当前筛选说明。
|
||||
var filterDescription: String {
|
||||
selectedFilter == .all ? "显示全部消息" : "仅显示未读消息"
|
||||
}
|
||||
|
||||
/// 切换筛选并重新加载首屏。
|
||||
func selectFilter(_ filter: MessageFilter, api: any MessageCenterServing) async {
|
||||
guard selectedFilter != filter else { return }
|
||||
selectedFilter = filter
|
||||
await reloadFirstPage(api: api)
|
||||
}
|
||||
|
||||
/// 重新加载首屏消息,失败时清空旧数据。
|
||||
func reloadFirstPage(api: any MessageCenterServing) async {
|
||||
isLoading = true
|
||||
loadFailed = false
|
||||
loadFailureReason = nil
|
||||
message = nil
|
||||
defer { isLoading = false }
|
||||
|
||||
do {
|
||||
let response = try await api.messageList(lastId: 0, limit: pageSize, unread: selectedFilter.rawValue)
|
||||
messages = mapMessages(response.items)
|
||||
hasMoreMessages = response.hasMore
|
||||
lastId = response.lastId
|
||||
} catch {
|
||||
clearMessages()
|
||||
loadFailed = true
|
||||
loadFailureReason = error.localizedDescription
|
||||
message = error.localizedDescription
|
||||
}
|
||||
}
|
||||
|
||||
/// 加载更多消息,失败时保留当前数据和分页状态。
|
||||
func loadMore(api: any MessageCenterServing) async {
|
||||
guard hasMoreMessages, !isLoadingMore, !isLoading else { return }
|
||||
isLoadingMore = true
|
||||
message = nil
|
||||
defer { isLoadingMore = false }
|
||||
|
||||
do {
|
||||
let response = try await api.messageList(lastId: lastId, limit: pageSize, unread: selectedFilter.rawValue)
|
||||
messages.append(contentsOf: mapMessages(response.items))
|
||||
messages = deduplicatedAndSorted(messages)
|
||||
hasMoreMessages = response.hasMore
|
||||
lastId = response.lastId
|
||||
} catch {
|
||||
message = error.localizedDescription
|
||||
}
|
||||
}
|
||||
|
||||
/// 标记单条消息为已读,接口成功后再更新本地状态。
|
||||
func markAsRead(api: any MessageCenterServing, item: MessageItem) async throws {
|
||||
guard !item.isRead, let msgId = item.msgId else { return }
|
||||
try await api.messageRead(id: msgId)
|
||||
guard let index = messages.firstIndex(where: { $0.id == item.id }) else { return }
|
||||
messages[index].isRead = true
|
||||
}
|
||||
|
||||
/// 将当前列表中的未读消息逐条标记为已读。
|
||||
func markAllAsRead(api: any MessageCenterServing) async throws {
|
||||
let original = messages
|
||||
do {
|
||||
for item in messages where !item.isRead {
|
||||
if let msgId = item.msgId {
|
||||
try await api.messageRead(id: msgId)
|
||||
}
|
||||
}
|
||||
messages = messages.map { item in
|
||||
var next = item
|
||||
next.isRead = true
|
||||
return next
|
||||
}
|
||||
} catch {
|
||||
messages = original
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/// 删除消息,接口成功后再移除本地列表。
|
||||
func deleteMessage(api: any MessageCenterServing, item: MessageItem) async throws {
|
||||
if let msgId = item.msgId {
|
||||
try await api.messageDelete(id: msgId)
|
||||
}
|
||||
messages.removeAll { $0.id == item.id }
|
||||
}
|
||||
|
||||
/// 将后端实体转换为页面展示实体。
|
||||
func mapMessages(_ source: [MessageEntity]) -> [MessageItem] {
|
||||
source.map { item in
|
||||
MessageItem(
|
||||
id: "msg_\(item.id)",
|
||||
msgId: item.id,
|
||||
title: item.title.trimmingCharacters(in: .whitespacesAndNewlines).nonEmpty
|
||||
?? item.typeName.trimmingCharacters(in: .whitespacesAndNewlines).nonEmpty
|
||||
?? "系统通知",
|
||||
detail: item.content,
|
||||
time: item.pushAt.trimmingCharacters(in: .whitespacesAndNewlines).nonEmpty
|
||||
?? item.createdAt.trimmingCharacters(in: .whitespacesAndNewlines).nonEmpty
|
||||
?? "0000-00-00 00:00",
|
||||
isRead: item.isRead,
|
||||
type: Self.messageType(from: item.type)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// 将后端类型映射为页面消息类型。
|
||||
static func messageType(from value: Int) -> MessageType {
|
||||
switch value {
|
||||
case 1:
|
||||
return .order
|
||||
case 2:
|
||||
return .writeOff
|
||||
default:
|
||||
return .system
|
||||
}
|
||||
}
|
||||
|
||||
private func deduplicatedAndSorted(_ source: [MessageItem]) -> [MessageItem] {
|
||||
var unique: [String: MessageItem] = [:]
|
||||
for item in source {
|
||||
unique[item.id] = item
|
||||
}
|
||||
return unique.values.sorted { $0.time > $1.time }
|
||||
}
|
||||
|
||||
private func clearMessages() {
|
||||
messages = []
|
||||
hasMoreMessages = false
|
||||
lastId = 0
|
||||
isLoadingMore = false
|
||||
}
|
||||
}
|
||||
|
||||
private extension String {
|
||||
var nonEmpty: String? {
|
||||
isEmpty ? nil : self
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user