// // InviteViewModels.swift // suixinkan // // Created by Codex on 2026/6/24. // import CoreImage.CIFilterBuiltins import Foundation import UIKit /// 邀请页 ViewModel,负责加载邀请信息、生成二维码和复制内容。 @MainActor final class PhotographerInviteViewModel { var onChange: (() -> Void)? private(set) var loading = false { didSet { onChange?() } } private(set) var inviteCode = "" { didSet { onChange?() } } private(set) var inviteUrl = "" { didSet { onChange?() } } private(set) var rules: [String] = [] { didSet { onChange?() } } private(set) var qrImage: UIImage? { didSet { onChange?() } } var errorMessage: String? { didSet { onChange?() } } private let qrContext = CIContext() private let qrFilter = CIFilter.qrCodeGenerator() /// 重新加载邀请信息。 func reload(api: any InviteServing) async { loading = true errorMessage = nil defer { loading = false } do { let response = try await api.inviteInfo() inviteCode = response.inviteCode inviteUrl = response.inviteUrl rules = response.description qrImage = makeQrImage(from: response.inviteUrl) } catch { clear() errorMessage = error.localizedDescription } } /// 复制邀请码。 func copyInviteCode() { UIPasteboard.general.string = inviteCode } /// 复制邀请链接。 func copyInviteUrl() { UIPasteboard.general.string = inviteUrl } /// 根据文本生成二维码图片。 func makeQrImage(from text: String) -> UIImage? { guard !text.isEmpty else { return nil } qrFilter.setValue(Data(text.utf8), forKey: "inputMessage") qrFilter.setValue("M", forKey: "inputCorrectionLevel") guard let output = qrFilter.outputImage else { return nil } let scaled = output.transformed(by: CGAffineTransform(scaleX: 12, y: 12)) guard let cgImage = qrContext.createCGImage(scaled, from: scaled.extent) else { return nil } return UIImage(cgImage: cgImage) } /// 清空。 private func clear() { inviteCode = "" inviteUrl = "" rules = [] qrImage = nil } } /// 邀请记录 ViewModel,负责邀请用户、奖励明细和钱包汇总分页。 @MainActor final class InviteRecordViewModel { var onChange: (() -> Void)? var tab: InviteRecordTab = .invite { didSet { onChange?() } } private(set) var totalRewardText = "¥ 0.00" { didSet { onChange?() } } private(set) var withdrawableText = "¥ 0.00" { didSet { onChange?() } } private(set) var displayRows: [InviteDisplayRow] = [] { didSet { onChange?() } } private(set) var loading = false { didSet { onChange?() } } private(set) var loadingMore = false { didSet { onChange?() } } private(set) var hasMore = false { didSet { onChange?() } } var errorMessage: String? { didSet { onChange?() } } private var inviteRows: [InviteDisplayRow] = [] private var rewardRows: [InviteDisplayRow] = [] private var invitePage = 1 private var rewardPage = 1 private let invitePageSize = 20 private let rewardPageSize = 10 /// 重新加载或追加邀请记录数据。 func reload(inviteAPI: any InviteServing, walletAPI: any WalletServing, refresh: Bool) async { if refresh { loading = true } else { guard hasMore else { return } loadingMore = true } errorMessage = nil defer { loading = false loadingMore = false } do { async let summary = walletAPI.walletSummary(type: 2) if tab == .invite { try await loadInviteRows(api: inviteAPI, refresh: refresh) } else { try await loadRewardRows(api: walletAPI, refresh: refresh) } let summaryValue = try await summary totalRewardText = "¥ \(summaryValue.amountTotal)" withdrawableText = "¥ \(summaryValue.amountWithdrawable)" displayRows = tab == .invite ? inviteRows : rewardRows } catch { if refresh { clear() } errorMessage = error.localizedDescription } } /// 切换邀请记录分段并刷新。 func selectTab(_ newTab: InviteRecordTab, inviteAPI: any InviteServing, walletAPI: any WalletServing) async { tab = newTab await reload(inviteAPI: inviteAPI, walletAPI: walletAPI, refresh: true) } /// 加载InviteRows数据。 private func loadInviteRows(api: any InviteServing, refresh: Bool) async throws { let users = try await api.inviteUserList(page: refresh ? 1 : invitePage, pageSize: invitePageSize) let mapped = users.map { InviteDisplayRow( id: "invite_\($0.id)", title: $0.realName.isEmpty ? "**\($0.phone.suffix(4))" : $0.realName, subtitle: "", amount: "", time: $0.createdAt, phone: $0.phone, avatar: $0.avatar, inviteLevel: $0.inviteLevel ) } if refresh { inviteRows = mapped invitePage = 2 } else { inviteRows.append(contentsOf: mapped) invitePage += 1 } hasMore = users.count >= invitePageSize } /// 加载RewardRows数据。 private func loadRewardRows(api: any WalletServing, refresh: Bool) async throws { let response = try await api.walletEarningDetail( startDate: "2025-01-01", endDate: Self.dayFormatter.string(from: Date()), page: refresh ? 1 : rewardPage, pageSize: rewardPageSize ) let mapped = response.list.flatMap { group in group.items.map { InviteDisplayRow( id: "reward_\($0.id)", title: $0.typeLabel.isEmpty ? "奖励记录" : $0.typeLabel, subtitle: $0.withdrawLabel ?? "订单尾号:\($0.orderNumberSuffix.isEmpty ? "--" : $0.orderNumberSuffix)", amount: $0.amount.isEmpty ? "--" : $0.amount, time: $0.createdAt.isEmpty ? group.date : $0.createdAt, phone: "", avatar: "", inviteLevel: 0 ) } } if refresh { rewardRows = mapped rewardPage = 2 } else { rewardRows.append(contentsOf: mapped) rewardPage += 1 } hasMore = rewardRows.count < response.total && !mapped.isEmpty } /// 清空。 private func clear() { totalRewardText = "¥ 0.00" withdrawableText = "¥ 0.00" displayRows = [] inviteRows = [] rewardRows = [] invitePage = 1 rewardPage = 1 hasMore = false } private static let dayFormatter: DateFormatter = { let formatter = DateFormatter() formatter.locale = Locale(identifier: "zh_CN") formatter.dateFormat = "yyyy-MM-dd" return formatter }() }