Files
suixinkan_uikit/suixinkan/UI/Orders/OrderCardCell.swift
汉秋 87696a4774 Introduce unified design tokens and migrate Home, Orders, and Statistics UI.
Establish AppColor/AppFont/AppSpacing/AppRadius theming, shared components, and design-system docs so pilot screens align with Android while keeping iOS-native spacing and touch targets.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-06 17:58:18 +08:00

315 lines
14 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// OrderCardCell.swift
// suixinkan
//
import SnapKit
import UIKit
struct OrderCardActions {
var onRefund: (() -> Void)?
var onVerify: (() -> Void)?
var onUploadTask: (() -> Void)?
var onCancel: (() -> Void)?
var onCallPhone: (() -> Void)?
var onGiftPhotoMinus: (() -> Void)?
var onGiftPhotoPlus: (() -> Void)?
var onGiftVideoMinus: (() -> Void)?
var onGiftVideoPlus: (() -> Void)?
var onSendGift: (() -> Void)?
var onRefinedChanged: ((Int) -> Void)?
var onHistoricalPhoto: (() -> Void)?
var onOrderSourceTap: (() -> Void)?
}
/// Android `OrderView`
final class OrderCardCell: UITableViewCell {
static let reuseIdentifier = "OrderCardCell"
private let cardView = UIView()
private let orderNumberLabel = UILabel()
private let statusBadge = OrderStatusBadgeView()
private let createdAtLabel = UILabel()
private let typeChip = UILabel()
private let actionStack = UIStackView()
private let refundButton = UIButton(type: .system)
private let verifyButton = UIButton(type: .system)
private let uploadTaskButton = UIButton(type: .system)
private let infoStack = UIStackView()
private let cancelButton = UIButton(type: .system)
private let orderSourceButton = UIButton(type: .system)
private let historicalButton = UIButton(type: .system)
private let refinedStack = UIStackView()
private let refinedNeedButton = UIButton(type: .system)
private let refinedNoButton = UIButton(type: .system)
private var actions = OrderCardActions()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
selectionStyle = .none
backgroundColor = .clear
contentView.backgroundColor = .clear
setupUI()
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(order: OrderEntity, hideWriteActions: Bool, actions: OrderCardActions, showOrderSource: Bool, sourceText: String?) {
self.actions = actions
orderNumberLabel.text = "订单号:\(order.orderNumber)"
statusBadge.apply(status: order.orderStatus, text: order.orderStatusName)
createdAtLabel.text = order.createdAt.isEmpty ? "--" : order.createdAt
typeChip.text = order.orderTypeLabel
typeChip.isHidden = order.orderTypeLabel.isEmpty
let showRefund = !hideWriteActions && (order.orderStatus == 18 || order.orderStatus == 30) && order.orderType != 19
let showVerify = !hideWriteActions && (
(order.photoTravel?.needCheckIn == true && order.orderStatus != 30) || order.orderType == 19
)
refundButton.isHidden = !showRefund
verifyButton.isHidden = !showVerify
actionStack.isHidden = !showRefund && !showVerify
uploadTaskButton.isHidden = hideWriteActions || order.orderType != 19
orderSourceButton.isHidden = !showOrderSource
orderSourceButton.setTitle(sourceText ?? "选择订单来源", for: .normal)
let showRefined = order.orderStatus == 18 || order.orderStatus == 30
refinedStack.isHidden = !showRefined
updateRefinedButtons(isRefined: order.isRefined, readOnly: hideWriteActions)
cancelButton.isHidden = hideWriteActions || order.photoTravel?.canCancelOrder != true
infoStack.arrangedSubviews.forEach { $0.removeFromSuperview() }
addInfoRow(title: "付款金额", value: "¥\(order.actualPayAmount)")
if !order.payTime.isEmpty { addInfoRow(title: "付款时间", value: order.payTime) }
if !order.completeTime.isEmpty { addInfoRow(title: "完成时间", value: order.completeTime) }
if !order.payTypeName.isEmpty { addInfoRow(title: "付款方式", value: order.payTypeName) }
addInfoRow(title: "UID", value: String(order.userId))
addPhoneRow(phone: order.phone, hidden: hideWriteActions)
if let travel = order.photoTravel, !travel.checkInTime.isEmpty {
addInfoRow(title: "核销时间", value: travel.checkInTime)
}
if let travel = order.photoTravel {
addInfoRow(title: "修图张数", value: "\(travel.orderPhotoNum)")
addInfoRow(title: "视频", value: "\(travel.orderVideoNum)")
}
if let remark = order.remark, !remark.isEmpty {
addInfoRow(title: "备注", value: remark)
}
let materials = order.multiTravel?.materialList ?? []
historicalButton.isHidden = materials.isEmpty
}
private func setupUI() {
cardView.backgroundColor = AppColor.cardBackground
cardView.layer.cornerRadius = AppRadius.lg
orderNumberLabel.font = .app(.body)
orderNumberLabel.textColor = AppColor.textPrimary
createdAtLabel.font = .app(.caption)
createdAtLabel.textColor = AppColor.textSecondary
typeChip.font = .app(.caption)
typeChip.textColor = AppColor.primary
typeChip.backgroundColor = AppColor.primaryLight
typeChip.layer.cornerRadius = AppRadius.xs
typeChip.clipsToBounds = true
typeChip.textAlignment = .center
configureActionButton(refundButton, title: "退款", color: AppColor.danger, bg: AppColor.dangerBackground)
configureActionButton(verifyButton, title: "核销订单", color: .white, bg: AppColor.primary)
refundButton.addTarget(self, action: #selector(refundTapped), for: .touchUpInside)
verifyButton.addTarget(self, action: #selector(verifyTapped), for: .touchUpInside)
uploadTaskButton.setTitle("上传任务", for: .normal)
uploadTaskButton.setTitleColor(AppColor.primary, for: .normal)
uploadTaskButton.titleLabel?.font = .app(.subtitle)
uploadTaskButton.backgroundColor = AppColor.primaryLight
uploadTaskButton.layer.cornerRadius = AppRadius.lg
uploadTaskButton.addTarget(self, action: #selector(uploadTaskTapped), for: .touchUpInside)
actionStack.axis = .horizontal
actionStack.spacing = AppSpacing.sm
actionStack.distribution = .fillEqually
actionStack.addArrangedSubview(refundButton)
actionStack.addArrangedSubview(verifyButton)
infoStack.axis = .vertical
infoStack.spacing = 6
cancelButton.setTitle("取消订单", for: .normal)
cancelButton.setTitleColor(AppColor.danger, for: .normal)
cancelButton.titleLabel?.font = .app(.body)
cancelButton.addTarget(self, action: #selector(cancelTapped), for: .touchUpInside)
orderSourceButton.setTitleColor(AppColor.primary, for: .normal)
orderSourceButton.titleLabel?.font = .app(.body)
orderSourceButton.contentHorizontalAlignment = .leading
orderSourceButton.addTarget(self, action: #selector(orderSourceTapped), for: .touchUpInside)
historicalButton.setTitle("历史拍摄", for: .normal)
historicalButton.setTitleColor(AppColor.primary, for: .normal)
historicalButton.titleLabel?.font = .app(.body)
historicalButton.addTarget(self, action: #selector(historicalTapped), for: .touchUpInside)
refinedStack.axis = .horizontal
refinedStack.spacing = AppSpacing.xs
refinedNeedButton.setTitle("需要精修", for: .normal)
refinedNoButton.setTitle("无需精修", for: .normal)
[refinedNeedButton, refinedNoButton].forEach {
$0.titleLabel?.font = .app(.body)
$0.layer.cornerRadius = AppRadius.sm
$0.layer.borderWidth = 1
}
refinedNeedButton.addTarget(self, action: #selector(refinedNeedTapped), for: .touchUpInside)
refinedNoButton.addTarget(self, action: #selector(refinedNoTapped), for: .touchUpInside)
refinedStack.addArrangedSubview(refinedNeedButton)
refinedStack.addArrangedSubview(refinedNoButton)
contentView.addSubview(cardView)
cardView.addSubview(orderNumberLabel)
cardView.addSubview(statusBadge)
cardView.addSubview(createdAtLabel)
cardView.addSubview(typeChip)
cardView.addSubview(orderSourceButton)
cardView.addSubview(actionStack)
cardView.addSubview(uploadTaskButton)
cardView.addSubview(refinedStack)
cardView.addSubview(infoStack)
cardView.addSubview(historicalButton)
cardView.addSubview(cancelButton)
cardView.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(UIEdgeInsets(top: 0, left: AppSpacing.md, bottom: AppSpacing.sm, right: AppSpacing.md))
}
orderNumberLabel.snp.makeConstraints { make in
make.top.leading.equalToSuperview().inset(16)
}
statusBadge.snp.makeConstraints { make in
make.centerY.equalTo(orderNumberLabel)
make.trailing.equalToSuperview().inset(16)
}
createdAtLabel.snp.makeConstraints { make in
make.top.equalTo(orderNumberLabel.snp.bottom).offset(4)
make.leading.equalToSuperview().inset(16)
}
typeChip.snp.makeConstraints { make in
make.top.equalTo(createdAtLabel.snp.bottom).offset(4)
make.leading.equalToSuperview().inset(16)
make.height.equalTo(20)
make.width.greaterThanOrEqualTo(40)
}
orderSourceButton.snp.makeConstraints { make in
make.top.equalTo(typeChip.snp.bottom).offset(8)
make.leading.trailing.equalToSuperview().inset(16)
}
actionStack.snp.makeConstraints { make in
make.top.equalTo(orderSourceButton.snp.bottom).offset(12)
make.leading.trailing.equalToSuperview().inset(16)
make.height.equalTo(48)
}
uploadTaskButton.snp.makeConstraints { make in
make.top.equalTo(actionStack.snp.bottom).offset(12)
make.leading.trailing.equalToSuperview().inset(16)
make.height.equalTo(48)
}
refinedStack.snp.makeConstraints { make in
make.top.equalTo(uploadTaskButton.snp.bottom).offset(12)
make.leading.equalToSuperview().inset(16)
}
infoStack.snp.makeConstraints { make in
make.top.equalTo(refinedStack.snp.bottom).offset(12)
make.leading.trailing.equalToSuperview().inset(16)
}
historicalButton.snp.makeConstraints { make in
make.top.equalTo(infoStack.snp.bottom).offset(8)
make.leading.equalToSuperview().inset(16)
}
cancelButton.snp.makeConstraints { make in
make.top.equalTo(historicalButton.snp.bottom).offset(12)
make.leading.equalToSuperview().inset(16)
make.bottom.equalToSuperview().inset(16)
}
}
private func configureActionButton(_ button: UIButton, title: String, color: UIColor, bg: UIColor) {
button.setTitle(title, for: .normal)
button.setTitleColor(color, for: .normal)
button.titleLabel?.font = .app(.subtitle)
button.backgroundColor = bg
button.layer.cornerRadius = AppRadius.sm
}
private func addInfoRow(title: String, value: String) {
let row = UIStackView()
row.axis = .horizontal
row.distribution = .fillEqually
let titleLabel = UILabel()
titleLabel.text = title
titleLabel.font = .app(.body)
titleLabel.textColor = AppColor.textSecondary
let valueLabel = UILabel()
valueLabel.text = value
valueLabel.font = .app(.body)
valueLabel.textColor = AppColor.textPrimary
valueLabel.textAlignment = .right
row.addArrangedSubview(titleLabel)
row.addArrangedSubview(valueLabel)
infoStack.addArrangedSubview(row)
}
private func addPhoneRow(phone: String, hidden: Bool) {
let row = UIStackView()
row.axis = .horizontal
row.distribution = .fillEqually
let titleLabel = UILabel()
titleLabel.text = "手机号"
titleLabel.font = .app(.body)
titleLabel.textColor = AppColor.textSecondary
let valueButton = UIButton(type: .system)
valueButton.setTitle(OrderPhoneMask.mask(phone), for: .normal)
valueButton.setTitleColor(AppColor.primary, for: .normal)
valueButton.titleLabel?.font = .app(.body)
valueButton.contentHorizontalAlignment = .trailing
valueButton.isHidden = hidden || phone.isEmpty
valueButton.addTarget(self, action: #selector(callTapped), for: .touchUpInside)
row.addArrangedSubview(titleLabel)
row.addArrangedSubview(valueButton)
infoStack.addArrangedSubview(row)
}
private func updateRefinedButtons(isRefined: Int, readOnly: Bool) {
let selectedBorder = AppColor.primary.cgColor
let normalBorder = AppColor.border.cgColor
refinedNeedButton.layer.borderColor = isRefined == 1 ? selectedBorder : normalBorder
refinedNoButton.layer.borderColor = isRefined == 2 ? selectedBorder : normalBorder
refinedNeedButton.isEnabled = !readOnly
refinedNoButton.isEnabled = !readOnly
}
@objc private func refundTapped() { actions.onRefund?() }
@objc private func verifyTapped() { actions.onVerify?() }
@objc private func uploadTaskTapped() { actions.onUploadTask?() }
@objc private func cancelTapped() { actions.onCancel?() }
@objc private func callTapped() { actions.onCallPhone?() }
@objc private func orderSourceTapped() { actions.onOrderSourceTap?() }
@objc private func historicalTapped() { actions.onHistoricalPhoto?() }
@objc private func refinedNeedTapped() { actions.onRefinedChanged?(1) }
@objc private func refinedNoTapped() { actions.onRefinedChanged?(2) }
}
enum OrderPhoneMask {
static func mask(_ phone: String) -> String {
let trimmed = phone.trimmingCharacters(in: .whitespacesAndNewlines)
guard trimmed.count >= 7 else { return trimmed }
let start = trimmed.prefix(3)
let end = trimmed.suffix(4)
return "\(start)****\(end)"
}
}