feat: 添加获客员分成记录并重构列表

This commit is contained in:
2026-07-23 17:43:13 +08:00
parent 7ccbc5568d
commit 0e0415eec4
7 changed files with 945 additions and 95 deletions

View File

@ -0,0 +1,94 @@
//
// CommissionRateLogViewModel.swift
// suixinkan
//
import Foundation
/// ViewModel
final class CommissionRateLogViewModel {
private enum Constants {
static let pageSize = 10
}
private(set) var items: [CommissionRateLogEntity] = []
private(set) var isRefreshing = false
private(set) var isLoadingMore = false
private(set) var initialLoading = true
private(set) var canLoadMore = false
var onStateChange: (() -> Void)?
var onShowMessage: ((String) -> Void)?
private let saleUserId: Int
private var total = 0
private var lastLoadedPage = 0
private var isLoading = false
init(saleUserId: Int) {
self.saleUserId = saleUserId
}
///
func refresh(api: OrderAPI) async {
await load(api: api, page: 1, append: false)
}
///
func loadMore(api: OrderAPI) async {
guard canLoadMore, !isRefreshing, !isLoadingMore else { return }
await load(api: api, page: lastLoadedPage + 1, append: true)
}
private func load(api: OrderAPI, page: Int, append: Bool) async {
guard saleUserId > 0 else {
initialLoading = false
onShowMessage?("获客员信息无效")
notifyStateChange()
return
}
guard !isLoading else { return }
isLoading = true
if append {
isLoadingMore = true
} else {
isRefreshing = true
}
notifyStateChange()
defer {
isLoading = false
initialLoading = false
if append {
isLoadingMore = false
} else {
isRefreshing = false
}
notifyStateChange()
}
do {
let response = try await api.saleUserCommissionRateLogs(
saleUserId: saleUserId,
page: page,
pageSize: Constants.pageSize
)
if append {
let existingIDs = Set(items.map(\.id))
items.append(contentsOf: response.list.filter { !existingIDs.contains($0.id) })
} else {
var seenIDs = Set<Int>()
items = response.list.filter { seenIDs.insert($0.id).inserted }
}
total = max(0, response.total)
lastLoadedPage = page
canLoadMore = items.count < total && !response.list.isEmpty
} catch {
onShowMessage?(error.localizedDescription)
}
}
private func notifyStateChange() {
onStateChange?()
}
}

View File

@ -293,6 +293,28 @@ final class OrderAPI {
)
}
///
func saleUserCommissionRateLogs(
saleUserId: Int,
page: Int = 1,
pageSize: Int = 10
) async throws -> CommissionRateLogListResponse {
guard saleUserId > 0 else {
throw APIError.networkFailed("获客员信息无效")
}
return try await client.send(
APIRequest(
method: .get,
path: "/api/yf-handset-app/photog/sale-user/commission-rate/logs",
queryItems: [
URLQueryItem(name: "sale_user_id", value: String(saleUserId)),
URLQueryItem(name: "page", value: String(max(1, page))),
URLQueryItem(name: "page_size", value: String(min(max(1, pageSize), 50))),
]
)
)
}
func shootHistory(orderNumber: String) async throws -> MultiTravelShootHistoryResponse {
try await client.send(
APIRequest(

View File

@ -875,6 +875,93 @@ struct CooperativeSalerEntity: Decodable, Equatable {
}
}
///
struct CommissionRateLogListResponse: Decodable, Equatable {
let list: [CommissionRateLogEntity]
let total: Int
let page: Int
let pageSize: Int
enum CodingKeys: String, CodingKey {
case list
case total
case page
case pageSize = "page_size"
}
}
///
struct CommissionRateLogEntity: Decodable, Equatable, Hashable {
let id: Int
let bindingId: Int
let saleUserId: Int
let storeUserId: Int
let beforeRate: Int
let beforeRateLabel: String
let afterRate: Int
let afterRateLabel: String
let operatorType: String
let operatorId: Int
let operatorName: String
let remark: String
let createdAt: String
enum CodingKeys: String, CodingKey {
case id
case bindingId = "binding_id"
case saleUserId = "sale_user_id"
case storeUserId = "store_user_id"
case beforeRate = "before_rate"
case beforeRateLabel = "before_rate_label"
case afterRate = "after_rate"
case afterRateLabel = "after_rate_label"
case operatorType = "operator_type"
case operatorId = "operator_id"
case operatorName = "operator_name"
case remark
case createdAt = "created_at"
}
/// 使 label
var displayBeforeRate: String {
let label = beforeRateLabel.trimmingCharacters(in: .whitespacesAndNewlines)
return label.isEmpty ? "\(beforeRate)%" : label
}
/// 使 label
var displayAfterRate: String {
let label = afterRateLabel.trimmingCharacters(in: .whitespacesAndNewlines)
return label.isEmpty ? "\(afterRate)%" : label
}
///
var metadataLine: String {
formattedCreatedAt
}
///
var displayRemark: String? {
let value = remark.trimmingCharacters(in: .whitespacesAndNewlines)
return value.isEmpty ? nil : value
}
private var formattedCreatedAt: String {
let value = createdAt.trimmingCharacters(in: .whitespacesAndNewlines)
guard !value.isEmpty else { return "" }
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(identifier: "Asia/Shanghai")
for format in ["yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm"] {
formatter.dateFormat = format
if let date = formatter.date(from: value) {
formatter.dateFormat = "yyyy-MM-dd HH:mm"
return formatter.string(from: date)
}
}
return value
}
}
struct ReferralOrderListResponse: Decodable {
let total: Int
let list: [ReferralOrderEntity]