重建订单列表 UI 并严格对齐 Android 订单卡片

This commit is contained in:
2026-07-07 09:12:06 +08:00
parent 28dc004110
commit 3bc017260f
17 changed files with 1235 additions and 379 deletions

View File

@ -0,0 +1,30 @@
//
// DepositOrderStatusBadgeView.swift
// suixinkan
//
import SnapKit
import UIKit
/// Android `DepositOrderCard` badge12sp
final class DepositOrderStatusBadgeView: UIView {
private let badge = AppStatusBadge()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(badge)
badge.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func apply(status: Int, text: String) {
badge.applyDepositOrderStatus(status, text: text)
}
}

View File

@ -0,0 +1,49 @@
//
// OrderActionButton.swift
// suixinkan
//
import SnapKit
import UIKit
/// Android `AppButton`
final class OrderActionButton: UIButton {
enum Style {
case destructiveLight
case primaryFilled
case primaryLight
case dangerFilled
}
init(title: String, style: Style) {
super.init(frame: .zero)
setTitle(title, for: .normal)
titleLabel?.font = .app(.subtitle)
layer.cornerRadius = AppRadius.sm
clipsToBounds = true
switch style {
case .destructiveLight:
setTitleColor(AppColor.danger, for: .normal)
backgroundColor = AppColor.dangerBackground
case .primaryFilled:
setTitleColor(.white, for: .normal)
backgroundColor = AppColor.primary
case .primaryLight:
setTitleColor(AppColor.primary, for: .normal)
backgroundColor = OrderTokens.chipBackground
layer.cornerRadius = AppRadius.lg
case .dangerFilled:
setTitleColor(.white, for: .normal)
backgroundColor = AppColor.danger
}
snp.makeConstraints { make in
make.height.equalTo(OrderTokens.actionButtonHeight)
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

View File

@ -0,0 +1,131 @@
//
// OrderEditOptionView.swift
// suixinkan
//
import SnapKit
import UIKit
/// / Android `OrderEditOptionSection`
final class OrderEditOptionView: UIView {
var onSelectionChange: ((Int) -> Void)?
private let titleLabel = UILabel()
private let readOnlyLabel = UILabel()
private let yesButton = UIControl()
private let noButton = UIControl()
private let yesCircle = UIView()
private let noCircle = UIView()
private let yesLabel = UILabel()
private let noLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
titleLabel.text = "需要剪辑修图"
titleLabel.font = .app(.body)
titleLabel.textColor = OrderTokens.label563
readOnlyLabel.font = .systemFont(ofSize: 14, weight: .medium)
readOnlyLabel.textColor = OrderTokens.valueBlack
readOnlyLabel.isHidden = true
[yesLabel, noLabel].forEach {
$0.text = $0 === yesLabel ? "" : ""
$0.font = .app(.body)
$0.textColor = AppColor.textPrimary
}
[yesCircle, noCircle].forEach {
$0.layer.cornerRadius = 10
$0.layer.borderWidth = 1.5
$0.layer.borderColor = AppColor.primary.cgColor
$0.snp.makeConstraints { make in
make.width.height.equalTo(20)
}
}
configureChoiceButton(yesButton, circle: yesCircle, label: yesLabel, tag: 1)
configureChoiceButton(noButton, circle: noCircle, label: noLabel, tag: 2)
let choicesStack = UIStackView(arrangedSubviews: [yesButton, noButton])
choicesStack.axis = .horizontal
choicesStack.spacing = AppSpacing.md
addSubview(titleLabel)
addSubview(readOnlyLabel)
addSubview(choicesStack)
titleLabel.snp.makeConstraints { make in
make.leading.centerY.equalToSuperview()
}
readOnlyLabel.snp.makeConstraints { make in
make.trailing.centerY.equalToSuperview()
}
choicesStack.snp.makeConstraints { make in
make.trailing.centerY.equalToSuperview()
}
snp.makeConstraints { make in
make.height.greaterThanOrEqualTo(28)
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func apply(isRefined: Int, readOnly: Bool) {
readOnlyLabel.isHidden = !readOnly
yesButton.isHidden = readOnly
noButton.isHidden = readOnly
if readOnly {
readOnlyLabel.text = switch isRefined {
case 1: ""
case 2: ""
default: "--"
}
} else {
updateCircle(yesCircle, selected: isRefined == 1)
updateCircle(noCircle, selected: isRefined == 2)
}
}
private func configureChoiceButton(_ control: UIControl, circle: UIView, label: UILabel, tag: Int) {
control.tag = tag
control.addTarget(self, action: #selector(choiceTapped(_:)), for: .touchUpInside)
let row = UIStackView(arrangedSubviews: [circle, label])
row.axis = .horizontal
row.spacing = AppSpacing.xxs
row.isUserInteractionEnabled = false
control.addSubview(row)
row.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
private func updateCircle(_ circle: UIView, selected: Bool) {
if selected {
circle.backgroundColor = AppColor.primary
circle.layer.borderWidth = 0
let check = UIImageView(image: UIImage(systemName: "checkmark"))
check.tintColor = .white
check.tag = 999
circle.subviews.forEach { $0.removeFromSuperview() }
circle.addSubview(check)
check.snp.makeConstraints { make in
make.center.equalToSuperview()
make.width.height.equalTo(12)
}
} else {
circle.backgroundColor = .clear
circle.layer.borderWidth = 1.5
circle.layer.borderColor = AppColor.primary.cgColor
circle.subviews.forEach { $0.removeFromSuperview() }
}
}
@objc private func choiceTapped(_ sender: UIControl) {
onSelectionChange?(sender.tag)
}
}

View File

@ -0,0 +1,66 @@
//
// OrderFilterPillControl.swift
// suixinkan
//
import SnapKit
import UIKit
/// pill Android /
final class OrderFilterPillControl: UIButton {
private let pillTitleLabel = UILabel()
private let trailingIconView = UIImageView()
var pillTitle: String = "" {
didSet { pillTitleLabel.text = pillTitle }
}
var showsTrailingIcon: Bool = false {
didSet { trailingIconView.isHidden = !showsTrailingIcon }
}
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = OrderTokens.filterBackground
layer.cornerRadius = OrderTokens.pillRadius
clipsToBounds = true
pillTitleLabel.font = .app(.body)
pillTitleLabel.textColor = OrderTokens.label563
pillTitleLabel.lineBreakMode = .byTruncatingTail
pillTitleLabel.isUserInteractionEnabled = false
trailingIconView.contentMode = .scaleAspectFit
trailingIconView.tintColor = OrderTokens.label563
trailingIconView.isHidden = true
trailingIconView.isUserInteractionEnabled = false
addSubview(pillTitleLabel)
addSubview(trailingIconView)
pillTitleLabel.snp.makeConstraints { make in
make.leading.equalToSuperview().inset(AppSpacing.sm)
make.centerY.equalToSuperview()
make.trailing.lessThanOrEqualTo(trailingIconView.snp.leading).offset(-AppSpacing.xxs)
}
trailingIconView.snp.makeConstraints { make in
make.trailing.equalToSuperview().inset(AppSpacing.sm)
make.centerY.equalToSuperview()
make.width.height.equalTo(15)
}
snp.makeConstraints { make in
make.height.equalTo(OrderTokens.filterPillHeight)
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setTrailingSystemImage(_ name: String) {
trailingIconView.image = UIImage(systemName: name)
showsTrailingIcon = true
}
}

View File

@ -0,0 +1,61 @@
//
// OrderGiftStepperView.swift
// suixinkan
//
import SnapKit
import UIKit
/// /+/- Android
final class OrderGiftStepperView: UIView {
var onMinusTap: (() -> Void)?
var onPlusTap: (() -> Void)?
private let minusButton = UIButton(type: .system)
private let plusButton = UIButton(type: .system)
private let valueLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
[minusButton, plusButton].forEach { button in
button.layer.cornerRadius = 12
button.layer.borderWidth = 1
button.layer.borderColor = OrderTokens.giftBorder.cgColor
button.titleLabel?.font = .systemFont(ofSize: 16, weight: .bold)
button.setTitleColor(OrderTokens.giftBorder, for: .normal)
button.snp.makeConstraints { make in
make.width.height.equalTo(24)
}
}
minusButton.setTitle("-", for: .normal)
plusButton.setTitle("+", for: .normal)
minusButton.addTarget(self, action: #selector(minusTapped), for: .touchUpInside)
plusButton.addTarget(self, action: #selector(plusTapped), for: .touchUpInside)
valueLabel.font = .systemFont(ofSize: 14, weight: .medium)
valueLabel.textColor = OrderTokens.valueBlack
valueLabel.textAlignment = .center
let stack = UIStackView(arrangedSubviews: [minusButton, valueLabel, plusButton])
stack.axis = .horizontal
stack.spacing = AppSpacing.xs
stack.alignment = .center
addSubview(stack)
stack.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func apply(text: String) {
valueLabel.text = text
}
@objc private func minusTapped() { onMinusTap?() }
@objc private func plusTapped() { onPlusTap?() }
}

View File

@ -0,0 +1,109 @@
//
// OrderHistoricalPhotosView.swift
// suixinkan
//
import SnapKit
import UIKit
/// Android multi_travel
final class OrderHistoricalPhotosView: UIView {
var onTap: (() -> Void)?
private let titleLabel = UILabel()
private let divider = UIView()
private let photosStack = UIStackView()
private var imageViews: [UIImageView] = []
private var overlayLabel: UILabel?
override init(frame: CGRect) {
super.init(frame: frame)
divider.backgroundColor = OrderTokens.remarkDivider
titleLabel.text = "历史拍摄信息:"
titleLabel.font = .app(.body)
titleLabel.textColor = OrderTokens.label563
photosStack.axis = .horizontal
photosStack.spacing = AppSpacing.xs
photosStack.distribution = .fillEqually
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap))
addGestureRecognizer(tap)
addSubview(divider)
addSubview(titleLabel)
addSubview(photosStack)
divider.snp.makeConstraints { make in
make.top.leading.trailing.equalToSuperview()
make.height.equalTo(1)
}
titleLabel.snp.makeConstraints { make in
make.top.equalTo(divider.snp.bottom).offset(AppSpacing.sm)
make.leading.trailing.equalToSuperview()
}
photosStack.snp.makeConstraints { make in
make.top.equalTo(titleLabel.snp.bottom).offset(AppSpacing.xs)
make.leading.trailing.bottom.equalToSuperview()
make.height.equalTo(72)
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func apply(materials: [MultiTravelMaterialEntity]) {
isHidden = materials.isEmpty
photosStack.arrangedSubviews.forEach { $0.removeFromSuperview() }
imageViews.removeAll()
overlayLabel?.removeFromSuperview()
overlayLabel = nil
let displayItems = Array(materials.prefix(3))
for (index, material) in displayItems.enumerated() {
let container = UIView()
container.backgroundColor = OrderTokens.filterBackground
container.layer.cornerRadius = AppRadius.sm
container.clipsToBounds = true
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
let thumbURL = material.coverUrl.isEmpty ? material.fileUrl : material.coverUrl
imageView.loadRemoteImage(urlString: thumbURL, placeholderColor: OrderTokens.filterBackground)
container.addSubview(imageView)
imageView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
imageViews.append(imageView)
photosStack.addArrangedSubview(container)
if materials.count > 3, index == 2 {
let overlay = UIView()
overlay.backgroundColor = UIColor.black.withAlphaComponent(0.5)
let label = UILabel()
label.text = "查看更多"
label.font = .systemFont(ofSize: 14, weight: .medium)
label.textColor = .white
label.textAlignment = .center
overlay.addSubview(label)
label.snp.makeConstraints { make in
make.center.equalToSuperview()
}
container.addSubview(overlay)
overlay.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
overlayLabel = label
}
}
}
@objc private func handleTap() {
onTap?()
}
}

View File

@ -0,0 +1,46 @@
//
// OrderInfoRowView.swift
// suixinkan
//
import SnapKit
import UIKit
/// key-value Android InfoRow
final class OrderInfoRowView: UIView {
private let titleLabel = UILabel()
private let valueLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
titleLabel.font = .app(.body)
titleLabel.textColor = OrderTokens.label563
valueLabel.font = .systemFont(ofSize: 14, weight: .medium)
valueLabel.textColor = OrderTokens.valueBlack
valueLabel.textAlignment = .right
valueLabel.numberOfLines = 0
addSubview(titleLabel)
addSubview(valueLabel)
titleLabel.snp.makeConstraints { make in
make.leading.top.bottom.equalToSuperview()
make.width.lessThanOrEqualToSuperview().multipliedBy(0.45)
}
valueLabel.snp.makeConstraints { make in
make.trailing.top.bottom.equalToSuperview()
make.leading.greaterThanOrEqualTo(titleLabel.snp.trailing).offset(AppSpacing.xs)
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func apply(title: String, value: String, valueColor: UIColor = OrderTokens.valueBlack, usesPrimaryValue: Bool = false) {
titleLabel.text = "\(title)"
valueLabel.text = value
valueLabel.textColor = usesPrimaryValue ? AppColor.primary : valueColor
}
}

View File

@ -0,0 +1,101 @@
//
// OrderPhoneRowView.swift
// suixinkan
//
import SnapKit
import UIKit
/// / Android
final class OrderPhoneRowView: UIView {
enum Style {
case photographer
case deposit
}
var onTap: (() -> Void)?
private let style: Style
private let titleLabel = UILabel()
private let phoneLabel = UILabel()
private let phoneButton = UIButton(type: .system)
private let iconButton = UIButton(type: .system)
init(style: Style) {
self.style = style
super.init(frame: .zero)
setupUI()
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func apply(phone: String, hidden: Bool = false) {
let masked = OrderPhoneMask.mask(phone)
phoneLabel.text = masked
isHidden = hidden || phone.isEmpty
}
private func setupUI() {
titleLabel.font = .app(.body)
titleLabel.textColor = OrderTokens.label563
titleLabel.text = "手机号:"
phoneLabel.font = .systemFont(ofSize: 14, weight: .medium)
phoneLabel.textAlignment = .right
phoneButton.addTarget(self, action: #selector(handleTap), for: .touchUpInside)
iconButton.addTarget(self, action: #selector(handleTap), for: .touchUpInside)
addSubview(titleLabel)
addSubview(phoneLabel)
addSubview(phoneButton)
addSubview(iconButton)
titleLabel.snp.makeConstraints { make in
make.leading.top.bottom.equalToSuperview()
}
switch style {
case .photographer:
phoneLabel.textColor = OrderTokens.valueBlack
iconButton.backgroundColor = OrderTokens.phoneIconBackground
iconButton.layer.cornerRadius = 12
iconButton.tintColor = AppColor.primary
iconButton.setImage(UIImage(systemName: "phone.fill"), for: .normal)
iconButton.snp.makeConstraints { make in
make.trailing.centerY.equalToSuperview()
make.width.height.equalTo(24)
}
phoneLabel.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.trailing.equalTo(iconButton.snp.leading).offset(-7)
}
case .deposit:
phoneLabel.textColor = AppColor.primary
iconButton.tintColor = AppColor.primary
iconButton.setImage(UIImage(systemName: "phone.fill"), for: .normal)
iconButton.snp.makeConstraints { make in
make.trailing.centerY.equalToSuperview()
make.width.height.equalTo(16)
}
phoneLabel.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.trailing.equalTo(iconButton.snp.leading).offset(-AppSpacing.xxs)
}
}
phoneButton.snp.makeConstraints { make in
make.leading.equalTo(phoneLabel)
make.trailing.equalTo(iconButton)
make.top.bottom.equalToSuperview()
}
}
@objc private func handleTap() {
onTap?()
}
}

View File

@ -0,0 +1,37 @@
//
// OrderTypeChipView.swift
// suixinkan
//
import SnapKit
import UIKit
/// chip
final class OrderTypeChipView: UIView {
private let label = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = OrderTokens.chipBackground
layer.cornerRadius = OrderTokens.pillRadius
clipsToBounds = true
label.font = .app(.caption)
label.textColor = AppColor.primary
addSubview(label)
label.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(UIEdgeInsets(top: 0, left: AppSpacing.xxs, bottom: 0, right: AppSpacing.xxs))
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func apply(text: String) {
label.text = text
isHidden = text.isEmpty
}
}