Initial commit: suixinkan_ios UIKit rewrite project.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-26 14:33:31 +08:00
commit 9edf993432
297 changed files with 47151 additions and 0 deletions

View File

@ -0,0 +1,60 @@
//
// WithdrawalAuditViewController.swift
// suixinkan
//
// Created by Codex on 2026/6/26.
//
import UIKit
///
final class WithdrawalAuditViewController: ModuleTableViewController {
private let viewModel = WithdrawalAuditViewModel()
override func viewDidLoad() {
title = "提现审核"
navigationItem.rightBarButtonItem = UIBarButtonItem(
title: "全部",
style: .plain,
target: self,
action: #selector(cycleFilter)
)
super.viewDidLoad()
wireViewModel(viewModel) { [weak self] in self?.updateFilterTitle() }
}
override func tableRowCount() -> Int {
viewModel.filteredRecords.count
}
override func configureCell(_ cell: TitleSubtitleTableViewCell, at indexPath: IndexPath) {
let record = viewModel.filteredRecords[indexPath.row]
cell.configure(title: "¥\(record.amount)", subtitle: record.statusLabel, detail: record.createdAt)
}
override func reloadContent() async {
await viewModel.reload(api: services.walletAPI)
updateFilterTitle()
}
override func willDisplayTableRow(at indexPath: IndexPath) {
guard indexPath.row >= viewModel.filteredRecords.count - 2 else { return }
Task { await viewModel.loadMore(api: services.walletAPI) }
}
@objc private func cycleFilter() {
let filters = WithdrawalAuditFilter.allCases
guard let index = filters.firstIndex(of: viewModel.selectedFilter) else { return }
let next = filters[(index + 1) % filters.count]
Task {
viewModel.selectFilter(next)
reloadTable()
}
}
private func updateFilterTitle() {
navigationItem.rightBarButtonItem?.title = viewModel.selectedFilter.title
}
}
extension WithdrawalAuditViewModel: ViewModelBindable {}

View File

@ -0,0 +1,138 @@
//
// WithdrawalAuditViewModel.swift
// suixinkan
//
// Created by Codex on 2026/6/25.
//
import Foundation
///
enum WithdrawalAuditFilter: String, CaseIterable, Identifiable {
case all
case processing
case completed
var id: String { rawValue }
///
var title: String {
switch self {
case .all: "全部"
case .processing: "处理中"
case .completed: "已完成"
}
}
}
@MainActor
/// ViewModel
final class WithdrawalAuditViewModel {
var onChange: (() -> Void)?
var records: [WalletWithdrawRecord] = [] { didSet { onChange?() } }
var selectedFilter: WithdrawalAuditFilter = .all { didSet { onChange?() } }
var isLoading = false { didSet { onChange?() } }
var isLoadingMore = false { didSet { onChange?() } }
var loadFailed = false { didSet { onChange?() } }
var loadFailureReason: String? { didSet { onChange?() } }
var errorMessage: String? { didSet { onChange?() } }
private let pageSize = 10
private var page = 1
private var total = 0
///
var filteredRecords: [WalletWithdrawRecord] {
filteredRecords(for: selectedFilter)
}
///
var hasMore: Bool {
records.count < total
}
/// 使 total
var totalCount: Int {
max(total, records.count)
}
///
var processingCount: Int {
filteredRecords(for: .processing).count
}
///
var completedCount: Int {
filteredRecords(for: .completed).count
}
///
func filteredRecords(for filter: WithdrawalAuditFilter) -> [WalletWithdrawRecord] {
switch filter {
case .all:
return records
case .processing:
return records.filter { record in
let status = record.statusLabel
return status.contains("") || status.contains("")
}
case .completed:
return records.filter { record in
let status = record.statusLabel
return status.contains("完成") || status.contains("到账") || status.contains("通过")
}
}
}
///
func selectFilter(_ filter: WithdrawalAuditFilter) {
selectedFilter = filter
}
///
func reload(api: WalletServing) async {
isLoading = true
loadFailed = false
loadFailureReason = nil
errorMessage = nil
defer { isLoading = false }
do {
let response = try await api.walletWithdrawList(page: 1, pageSize: pageSize)
records = response.item
total = response.total
page = 1
} catch {
clearRecords()
loadFailed = true
loadFailureReason = error.localizedDescription
errorMessage = error.localizedDescription
}
}
///
func loadMore(api: WalletServing) async {
guard hasMore, !isLoadingMore, !isLoading else { return }
isLoadingMore = true
errorMessage = nil
defer { isLoadingMore = false }
let nextPage = page + 1
do {
let response = try await api.walletWithdrawList(page: nextPage, pageSize: pageSize)
records += response.item
total = response.total
page = nextPage
} catch {
errorMessage = error.localizedDescription
}
}
///
private func clearRecords() {
records = []
page = 1
total = 0
isLoadingMore = false
}
}

View File

@ -0,0 +1,16 @@
# 提现审核模块
## 模块职责
`Features/WithdrawalAudit` 承接首页 `withdrawal_audit` 权限入口,以审核视角展示钱包提现记录、状态筛选和单笔处理进度。
## 业务流程
- `WithdrawalAuditView` 进入后调用 `WalletAPI.walletWithdrawList` 加载第一页提现记录。
- `WithdrawalAuditViewModel` 管理分页、加载更多、筛选状态和失败清理。
- 筛选只在本地执行:处理中匹配状态文案中的“中/待”,已完成匹配“完成/到账/通过”。
- 刷新失败会清空旧记录和分页状态,避免账号或权限切换后残留上一组财务数据。
## 边界
本模块不做管理员提现审批操作。旧 Android 工程未找到提现审批列表或操作接口,当前按旧 iOS 的提现记录审核视角迁移。