Integrate高德 SDK with simulator-safe build flags, add map views for operating area and punch points, refactor main tabs and key feature screens to UIKit with Diffable lists, and document Swift concurrency defaults in AGENTS.md. Co-authored-by: Cursor <cursoragent@cursor.com>
172 lines
5.7 KiB
Swift
172 lines
5.7 KiB
Swift
//
|
||
// MessageCenterViewModel.swift
|
||
// suixinkan
|
||
//
|
||
// Created by Codex on 2026/6/25.
|
||
//
|
||
|
||
import Foundation
|
||
|
||
@MainActor
|
||
/// 消息中心 ViewModel,负责消息分页、筛选、已读和删除。
|
||
final class MessageCenterViewModel {
|
||
var onChange: (() -> Void)?
|
||
var messages: [MessageItem] = [] { didSet { onChange?() } }
|
||
var selectedFilter: MessageFilter = .all { didSet { onChange?() } }
|
||
var isLoading = false { didSet { onChange?() } }
|
||
var isLoadingMore = false { didSet { onChange?() } }
|
||
var loadFailed = false { didSet { onChange?() } }
|
||
var loadFailureReason: String? { didSet { onChange?() } }
|
||
var message: String? { didSet { onChange?() } }
|
||
|
||
private(set) var hasMoreMessages = false { didSet { onChange?() } }
|
||
private(set) var lastId = 0 { didSet { onChange?() } }
|
||
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
|
||
}
|
||
}
|
||
|
||
/// deduplicated并排序相关逻辑。
|
||
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 }
|
||
}
|
||
|
||
/// 清空Messages。
|
||
private func clearMessages() {
|
||
messages = []
|
||
hasMoreMessages = false
|
||
lastId = 0
|
||
isLoadingMore = false
|
||
}
|
||
}
|
||
|
||
private extension String {
|
||
var nonEmpty: String? {
|
||
isEmpty ? nil : self
|
||
}
|
||
}
|