Files
suixinkan_uikit/suixinkan/UI/CooperationOrder/Views/CooperationOrderViews.swift
T

1044 lines
41 KiB
Swift

//
// CooperationOrderViews.swift
// suixinkan
//
import Kingfisher
import SnapKit
import UIKit
/// 合作订单 Tab 切换栏。
final class CooperationOrderTabBar: UIView {
var onTabSelected: ((Int) -> Void)?
private let stack = UIStackView()
private var buttons: [UIButton] = []
private var selectedIndex = 0
private let indicator = UIView()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .white
stack.axis = .horizontal
stack.distribution = .fillEqually
addSubview(stack)
addSubview(indicator)
indicator.backgroundColor = AppColor.primary
indicator.layer.cornerRadius = 1.5
["获客员线索", "获客订单"].enumerated().forEach { index, title in
let button = UIButton(type: .system)
button.setTitle(title, for: .normal)
button.titleLabel?.font = .systemFont(ofSize: 15, weight: index == 0 ? .bold : .regular)
button.setTitleColor(index == 0 ? AppColor.primary : AppColor.textSecondary, for: .normal)
button.tag = index
button.addTarget(self, action: #selector(tabTapped(_:)), for: .touchUpInside)
buttons.append(button)
stack.addArrangedSubview(button)
}
stack.snp.makeConstraints { make in
make.edges.equalToSuperview()
make.height.equalTo(48)
}
layoutIndicator(animated: false)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setSelectedTab(_ index: Int) {
guard selectedIndex != index else { return }
selectedIndex = index
updateButtonStyles()
layoutIndicator(animated: true)
}
@objc private func tabTapped(_ sender: UIButton) {
setSelectedTab(sender.tag)
onTabSelected?(sender.tag)
}
private func updateButtonStyles() {
buttons.enumerated().forEach { index, button in
let selected = index == selectedIndex
button.setTitleColor(selected ? AppColor.primary : AppColor.textSecondary, for: .normal)
button.titleLabel?.font = .systemFont(ofSize: 15, weight: selected ? .bold : .regular)
}
}
private func layoutIndicator(animated: Bool) {
guard !buttons.isEmpty else { return }
let button = buttons[selectedIndex]
let apply = {
self.indicator.snp.remakeConstraints { make in
make.bottom.equalToSuperview()
make.height.equalTo(3)
make.width.equalTo(button.snp.width).multipliedBy(0.5)
make.centerX.equalTo(button)
}
self.layoutIfNeeded()
}
if animated {
UIView.animate(withDuration: 0.2, animations: apply)
} else {
apply()
}
}
}
/// 获客订单搜索栏。
final class CooperationOrderSearchBar: UIView, UITextFieldDelegate {
var onSearchTextChange: ((String) -> Void)?
var onSearch: (() -> Void)?
let textField = UITextField()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .white
let container = UIView()
container.backgroundColor = AppColor.inputBackground
container.layer.cornerRadius = AppRadius.sm
let icon = UIImageView(image: UIImage(systemName: "magnifyingglass"))
icon.tintColor = AppColor.textTertiary
textField.placeholder = "名称搜索"
textField.font = .systemFont(ofSize: 14)
textField.textColor = AppColor.textPrimary
textField.returnKeyType = .search
textField.delegate = self
textField.addTarget(self, action: #selector(textChanged), for: .editingChanged)
addSubview(container)
container.addSubview(icon)
container.addSubview(textField)
container.snp.makeConstraints { make in
make.leading.trailing.equalToSuperview().inset(AppSpacing.md)
make.centerY.equalToSuperview()
make.height.equalTo(38)
}
icon.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(AppSpacing.sm)
make.centerY.equalToSuperview()
make.size.equalTo(18)
}
textField.snp.makeConstraints { make in
make.leading.equalTo(icon.snp.trailing).offset(AppSpacing.xs)
make.trailing.equalToSuperview().inset(AppSpacing.sm)
make.centerY.equalToSuperview()
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func textChanged() {
onSearchTextChange?(textField.text ?? "")
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
onSearch?()
return true
}
}
/// 获客员线索 Cell。
final class ReferralLeadCell: UITableViewCell {
static let reuseIdentifier = "ReferralLeadCell"
var onImageTap: ((Int, [String]) -> Void)?
private let cardView = UIView()
private let phoneLabel = UILabel()
private let phoneValueLabel = UILabel()
private let timeLabel = UILabel()
private let salerRow = CooperationInfoRowView(label: "获客员")
private let remarkTitleLabel = UILabel()
private let remarkLabel = UILabel()
private let imageTitleLabel = UILabel()
private let imageStack = UIStackView()
private let emptyImageLabel = UILabel()
private var imageURLs: [String] = []
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
selectionStyle = .none
backgroundColor = .clear
contentView.backgroundColor = .clear
cardView.backgroundColor = .white
cardView.layer.cornerRadius = AppRadius.lg
cardView.layer.shadowColor = UIColor.black.cgColor
cardView.layer.shadowOpacity = 0.04
cardView.layer.shadowOffset = CGSize(width: 0, height: 1)
cardView.layer.shadowRadius = 2
phoneLabel.text = "用户手机号"
phoneLabel.font = .systemFont(ofSize: 13)
phoneLabel.textColor = AppColor.textTertiary
phoneValueLabel.font = .systemFont(ofSize: 15, weight: .medium)
phoneValueLabel.textColor = AppColor.textPrimary
timeLabel.font = .systemFont(ofSize: 11)
timeLabel.textColor = AppColor.textTertiary
timeLabel.textAlignment = .right
remarkTitleLabel.text = "备注"
remarkTitleLabel.font = .systemFont(ofSize: 13)
remarkTitleLabel.textColor = AppColor.textTertiary
remarkLabel.font = .systemFont(ofSize: 14)
remarkLabel.textColor = AppColor.textPrimary
remarkLabel.numberOfLines = 0
imageTitleLabel.text = "图片"
imageTitleLabel.font = .systemFont(ofSize: 13)
imageTitleLabel.textColor = AppColor.textTertiary
imageStack.axis = .horizontal
imageStack.spacing = AppSpacing.xs
imageStack.distribution = .fillEqually
emptyImageLabel.text = "暂无图片"
emptyImageLabel.font = .systemFont(ofSize: 13)
emptyImageLabel.textColor = AppColor.textTertiary
emptyImageLabel.textAlignment = .center
emptyImageLabel.backgroundColor = UIColor(hex: 0xF7F8FA)
emptyImageLabel.layer.cornerRadius = AppRadius.sm
emptyImageLabel.clipsToBounds = true
emptyImageLabel.isHidden = true
contentView.addSubview(cardView)
cardView.addSubview(phoneLabel)
cardView.addSubview(phoneValueLabel)
cardView.addSubview(timeLabel)
cardView.addSubview(salerRow)
cardView.addSubview(remarkTitleLabel)
cardView.addSubview(remarkLabel)
cardView.addSubview(imageTitleLabel)
cardView.addSubview(imageStack)
cardView.addSubview(emptyImageLabel)
cardView.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(UIEdgeInsets(top: 0, left: AppSpacing.md, bottom: AppSpacing.sm, right: AppSpacing.md))
}
phoneLabel.snp.makeConstraints { make in
make.top.leading.equalToSuperview().offset(14)
}
phoneValueLabel.snp.makeConstraints { make in
make.leading.equalTo(phoneLabel.snp.trailing).offset(14)
make.centerY.equalTo(phoneLabel)
}
timeLabel.snp.makeConstraints { make in
make.trailing.equalToSuperview().inset(14)
make.centerY.equalTo(phoneLabel)
make.leading.greaterThanOrEqualTo(phoneValueLabel.snp.trailing).offset(AppSpacing.xs)
}
salerRow.snp.makeConstraints { make in
make.top.equalTo(phoneLabel.snp.bottom).offset(10)
make.leading.trailing.equalToSuperview().inset(14)
}
remarkTitleLabel.snp.makeConstraints { make in
make.top.equalTo(salerRow.snp.bottom).offset(10)
make.leading.equalToSuperview().offset(14)
}
remarkLabel.snp.makeConstraints { make in
make.top.equalTo(remarkTitleLabel.snp.bottom).offset(4)
make.leading.trailing.equalToSuperview().inset(14)
}
imageTitleLabel.snp.makeConstraints { make in
make.top.equalTo(remarkLabel.snp.bottom).offset(10)
make.leading.equalToSuperview().offset(14)
}
imageStack.snp.makeConstraints { make in
make.top.equalTo(imageTitleLabel.snp.bottom).offset(4)
make.leading.trailing.equalToSuperview().inset(14)
make.height.equalTo(imageStack.snp.width).multipliedBy(1.0 / 3.0).offset(-AppSpacing.xs * 2 / 3)
make.bottom.equalToSuperview().inset(14)
}
emptyImageLabel.snp.makeConstraints { make in
make.edges.equalTo(imageStack)
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(with lead: ReferralLeadEntity) {
phoneValueLabel.text = lead.displayPhone.isEmpty ? "—" : lead.displayPhone
timeLabel.text = lead.createdAt
salerRow.setValue(lead.displaySalerName.isEmpty ? "—" : lead.displaySalerName)
remarkLabel.text = lead.remark.isEmpty ? "暂无备注" : lead.remark
imageURLs = lead.displayImages
imageStack.arrangedSubviews.forEach { $0.removeFromSuperview() }
if imageURLs.isEmpty {
emptyImageLabel.isHidden = false
imageStack.isHidden = true
} else {
emptyImageLabel.isHidden = true
imageStack.isHidden = false
imageURLs.prefix(3).enumerated().forEach { index, url in
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
imageView.layer.cornerRadius = AppRadius.sm
imageView.backgroundColor = UIColor(hex: 0xF4F4F4)
imageView.isUserInteractionEnabled = true
imageView.loadRemoteImage(urlString: url)
imageView.tag = index
imageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(imageTapped(_:))))
imageStack.addArrangedSubview(imageView)
}
let missing = 3 - imageStack.arrangedSubviews.count
if missing > 0 {
(0..<missing).forEach { _ in
let spacer = UIView()
imageStack.addArrangedSubview(spacer)
}
}
}
}
@objc private func imageTapped(_ gesture: UITapGestureRecognizer) {
guard let index = gesture.view?.tag else { return }
onImageTap?(index, imageURLs)
}
}
/// 获客订单 Cell。
final class AcquisitionOrderCell: UITableViewCell {
static let reuseIdentifier = "AcquisitionOrderCell"
private let cardView = UIView()
private let titleLabel = UILabel()
private let typeTag = UILabel()
private let orderNumberLabel = UILabel()
private let partnerRow = CooperationInfoRowView(label: "合作人员")
private let timeRow = CooperationInfoRowView(label: "下单时间")
private let customerLabel = UILabel()
private let amountLabel = UILabel()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
selectionStyle = .none
backgroundColor = .clear
contentView.backgroundColor = .clear
cardView.backgroundColor = .white
cardView.layer.cornerRadius = AppRadius.lg
cardView.layer.shadowColor = UIColor.black.cgColor
cardView.layer.shadowOpacity = 0.04
cardView.layer.shadowOffset = CGSize(width: 0, height: 1)
cardView.layer.shadowRadius = 2
titleLabel.font = .systemFont(ofSize: 16, weight: .bold)
titleLabel.textColor = AppColor.textPrimary
typeTag.font = .systemFont(ofSize: 11)
typeTag.textColor = AppColor.primary
typeTag.backgroundColor = AppColor.primaryLight
typeTag.layer.cornerRadius = AppRadius.xs
typeTag.clipsToBounds = true
typeTag.textAlignment = .center
orderNumberLabel.font = .systemFont(ofSize: 13)
orderNumberLabel.textColor = AppColor.textSecondary
customerLabel.font = .systemFont(ofSize: 12)
customerLabel.textColor = AppColor.textTertiary
amountLabel.font = .systemFont(ofSize: 17, weight: .bold)
amountLabel.textColor = AppColor.primary
amountLabel.textAlignment = .right
contentView.addSubview(cardView)
cardView.addSubview(titleLabel)
cardView.addSubview(typeTag)
cardView.addSubview(orderNumberLabel)
cardView.addSubview(partnerRow)
cardView.addSubview(timeRow)
cardView.addSubview(customerLabel)
cardView.addSubview(amountLabel)
cardView.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(UIEdgeInsets(top: 0, left: AppSpacing.md, bottom: AppSpacing.sm, right: AppSpacing.md))
}
titleLabel.snp.makeConstraints { make in
make.top.leading.equalToSuperview().offset(14)
make.trailing.lessThanOrEqualToSuperview().inset(14)
}
typeTag.snp.makeConstraints { make in
make.top.equalTo(titleLabel.snp.bottom).offset(10)
make.leading.equalToSuperview().offset(14)
make.height.equalTo(22)
}
orderNumberLabel.snp.makeConstraints { make in
make.centerY.equalTo(typeTag)
make.leading.equalTo(typeTag.snp.trailing).offset(AppSpacing.xs)
make.trailing.lessThanOrEqualToSuperview().inset(14)
}
partnerRow.snp.makeConstraints { make in
make.top.equalTo(typeTag.snp.bottom).offset(10)
make.leading.trailing.equalToSuperview().inset(14)
}
timeRow.snp.makeConstraints { make in
make.top.equalTo(partnerRow.snp.bottom).offset(10)
make.leading.trailing.equalToSuperview().inset(14)
}
customerLabel.snp.makeConstraints { make in
make.top.equalTo(timeRow.snp.bottom).offset(10)
make.leading.equalToSuperview().offset(14)
make.bottom.equalToSuperview().inset(14)
}
amountLabel.snp.makeConstraints { make in
make.centerY.equalTo(customerLabel)
make.trailing.equalToSuperview().inset(14)
make.leading.greaterThanOrEqualTo(customerLabel.snp.trailing).offset(AppSpacing.xs)
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(with order: AcquisitionOrderEntity) {
titleLabel.text = order.projectName.isEmpty ? "—" : order.projectName
if order.orderTypeLabel.isEmpty {
typeTag.isHidden = true
orderNumberLabel.snp.remakeConstraints { make in
make.top.equalTo(titleLabel.snp.bottom).offset(10)
make.leading.equalToSuperview().offset(14)
make.trailing.lessThanOrEqualToSuperview().inset(14)
}
} else {
typeTag.isHidden = false
typeTag.text = " \(order.orderTypeLabel) "
orderNumberLabel.snp.remakeConstraints { make in
make.centerY.equalTo(typeTag)
make.leading.equalTo(typeTag.snp.trailing).offset(AppSpacing.xs)
make.trailing.lessThanOrEqualToSuperview().inset(14)
}
}
orderNumberLabel.text = "订单号 \(order.orderNumber)"
partnerRow.setValue(order.displayPartner.isEmpty ? "暂未绑定" : order.displayPartner)
timeRow.setValue(order.createdAt.isEmpty ? "—" : order.createdAt)
customerLabel.text = "客户 \(order.displayCustomerPhone.isEmpty ? "—" : order.displayCustomerPhone)"
amountLabel.text = "¥\(order.displayAmount)"
}
}
/// 合作获客员 Cell。
final class CooperationAcquirerCell: UITableViewCell {
static let reuseIdentifier = "CooperationAcquirerCell"
var onEditRemark: (() -> Void)?
var onEditCommission: (() -> Void)?
var onViewCommissionLogs: (() -> Void)?
var onCall: (() -> Void)?
private let cardView = UIView()
private let avatarContainer = UIView()
private let avatarIconView = UIImageView(image: UIImage(systemName: "person.fill"))
private let nameValueLabel = UILabel()
private let phoneValueLabel = UILabel()
private let callButton = UIButton(type: .system)
private let commissionContainer = UIView()
private let commissionTitleLabel = UILabel()
private let commissionValueLabel = UILabel()
private let bindTimeIconView = UIImageView(image: UIImage(systemName: "calendar"))
private let bindTimeValueLabel = UILabel()
private let dividerView = UIView()
private let editRemarkButton = UIButton(type: .system)
private let editCommissionButton = UIButton(type: .system)
private let commissionLogButton = UIButton(type: .system)
private let actionStack = UIStackView()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
selectionStyle = .none
backgroundColor = .clear
contentView.backgroundColor = .clear
cardView.backgroundColor = .white
cardView.layer.cornerRadius = AppRadius.xl
cardView.layer.borderWidth = 0.5
cardView.layer.borderColor = AppColor.cardOutline.cgColor
cardView.layer.shadowColor = UIColor.black.cgColor
cardView.layer.shadowOpacity = 0.04
cardView.layer.shadowOffset = CGSize(width: 0, height: 3)
cardView.layer.shadowRadius = 8
cardView.accessibilityIdentifier = "cooperation_acquirer_card"
avatarContainer.backgroundColor = AppColor.primary.withAlphaComponent(0.08)
avatarContainer.layer.cornerRadius = 24
avatarContainer.clipsToBounds = true
avatarIconView.tintColor = AppColor.primary
avatarIconView.contentMode = .scaleAspectFit
nameValueLabel.font = .systemFont(ofSize: 17, weight: .semibold)
nameValueLabel.textColor = AppColor.textPrimary
nameValueLabel.numberOfLines = 1
nameValueLabel.lineBreakMode = .byTruncatingTail
nameValueLabel.accessibilityIdentifier = "cooperation_acquirer_name"
phoneValueLabel.font = .systemFont(ofSize: 14)
phoneValueLabel.textColor = AppColor.textTertiary
phoneValueLabel.numberOfLines = 1
phoneValueLabel.lineBreakMode = .byTruncatingTail
phoneValueLabel.accessibilityIdentifier = "cooperation_acquirer_phone"
callButton.setImage(UIImage(systemName: "phone.fill"), for: .normal)
callButton.tintColor = AppColor.primary
callButton.backgroundColor = AppColor.primaryLight
callButton.layer.cornerRadius = AppSpacing.minTouchTarget / 2
callButton.layer.borderWidth = 0.5
callButton.layer.borderColor = AppColor.primary.withAlphaComponent(0.12).cgColor
callButton.imageView?.contentMode = .scaleAspectFit
callButton.accessibilityLabel = "拨打获客员电话"
callButton.accessibilityIdentifier = "cooperation_acquirer_call"
callButton.addTarget(self, action: #selector(callTapped), for: .touchUpInside)
commissionContainer.backgroundColor = AppColor.primary.withAlphaComponent(0.06)
commissionContainer.layer.cornerRadius = AppRadius.lg
commissionTitleLabel.text = "分成比例"
commissionTitleLabel.font = .systemFont(ofSize: 12)
commissionTitleLabel.textColor = AppColor.textTertiary
commissionValueLabel.font = .systemFont(ofSize: 24, weight: .bold)
commissionValueLabel.textColor = AppColor.primary
commissionValueLabel.numberOfLines = 1
commissionValueLabel.adjustsFontSizeToFitWidth = true
commissionValueLabel.minimumScaleFactor = 0.75
commissionValueLabel.accessibilityIdentifier = "cooperation_acquirer_commission_rate"
bindTimeIconView.tintColor = AppColor.textTertiary
bindTimeIconView.contentMode = .scaleAspectFit
bindTimeValueLabel.font = .systemFont(ofSize: 13)
bindTimeValueLabel.textColor = AppColor.textTertiary
bindTimeValueLabel.numberOfLines = 2
bindTimeValueLabel.lineBreakMode = .byTruncatingTail
bindTimeValueLabel.accessibilityIdentifier = "cooperation_acquirer_bind_time"
dividerView.backgroundColor = AppColor.cardOutline
configureActionButton(
editRemarkButton,
title: "修改备注",
isPrimary: false,
accessibilityIdentifier: "cooperation_acquirer_edit_remark"
)
editRemarkButton.addTarget(self, action: #selector(editRemarkTapped), for: .touchUpInside)
configureActionButton(
commissionLogButton,
title: "修改记录",
isPrimary: false,
accessibilityIdentifier: "cooperation_acquirer_commission_logs"
)
commissionLogButton.accessibilityLabel = "查看分成比例修改记录"
commissionLogButton.addTarget(self, action: #selector(commissionLogTapped), for: .touchUpInside)
configureActionButton(
editCommissionButton,
title: "修改比例",
isPrimary: true,
accessibilityIdentifier: "cooperation_acquirer_edit_commission"
)
editCommissionButton.addTarget(self, action: #selector(editCommissionTapped), for: .touchUpInside)
actionStack.axis = .horizontal
actionStack.alignment = .fill
actionStack.distribution = .fillEqually
actionStack.spacing = AppSpacing.xs
actionStack.addArrangedSubview(editRemarkButton)
actionStack.addArrangedSubview(commissionLogButton)
actionStack.addArrangedSubview(editCommissionButton)
contentView.addSubview(cardView)
cardView.addSubview(avatarContainer)
avatarContainer.addSubview(avatarIconView)
cardView.addSubview(nameValueLabel)
cardView.addSubview(phoneValueLabel)
cardView.addSubview(callButton)
cardView.addSubview(commissionContainer)
commissionContainer.addSubview(commissionTitleLabel)
commissionContainer.addSubview(commissionValueLabel)
cardView.addSubview(bindTimeIconView)
cardView.addSubview(bindTimeValueLabel)
cardView.addSubview(dividerView)
cardView.addSubview(actionStack)
cardView.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(
UIEdgeInsets(top: 0, left: AppSpacing.md, bottom: AppSpacing.sm, right: AppSpacing.md)
)
}
avatarContainer.snp.makeConstraints { make in
make.top.leading.equalToSuperview().inset(AppSpacing.md)
make.size.equalTo(48)
}
avatarIconView.snp.makeConstraints { make in
make.center.equalToSuperview()
make.size.equalTo(24)
}
nameValueLabel.snp.makeConstraints { make in
make.top.equalTo(avatarContainer).offset(2)
make.leading.equalTo(avatarContainer.snp.trailing).offset(AppSpacing.sm)
make.trailing.lessThanOrEqualTo(callButton.snp.leading).offset(-AppSpacing.sm)
}
phoneValueLabel.snp.makeConstraints { make in
make.top.equalTo(nameValueLabel.snp.bottom).offset(AppSpacing.xxs)
make.leading.equalTo(nameValueLabel)
make.trailing.lessThanOrEqualTo(callButton.snp.leading).offset(-AppSpacing.sm)
}
callButton.snp.makeConstraints { make in
make.top.trailing.equalToSuperview().inset(AppSpacing.md)
make.size.equalTo(AppSpacing.minTouchTarget)
}
commissionContainer.snp.makeConstraints { make in
make.top.equalTo(avatarContainer.snp.bottom).offset(14)
make.leading.equalToSuperview().inset(AppSpacing.md)
make.width.equalTo(120)
make.height.equalTo(64)
}
commissionTitleLabel.snp.makeConstraints { make in
make.top.equalToSuperview().offset(AppSpacing.xs)
make.centerX.equalToSuperview()
}
commissionValueLabel.snp.makeConstraints { make in
make.top.equalTo(commissionTitleLabel.snp.bottom).offset(2)
make.centerX.equalToSuperview()
make.leading.greaterThanOrEqualToSuperview().offset(AppSpacing.xs)
make.trailing.lessThanOrEqualToSuperview().inset(AppSpacing.xs)
}
bindTimeIconView.snp.makeConstraints { make in
make.leading.equalTo(commissionContainer.snp.trailing).offset(AppSpacing.md)
make.centerY.equalTo(commissionContainer)
make.size.equalTo(18)
}
bindTimeValueLabel.snp.makeConstraints { make in
make.leading.equalTo(bindTimeIconView.snp.trailing).offset(AppSpacing.xs)
make.trailing.equalToSuperview().inset(AppSpacing.md)
make.centerY.equalTo(bindTimeIconView)
}
dividerView.snp.makeConstraints { make in
make.top.equalTo(commissionContainer.snp.bottom).offset(14)
make.leading.trailing.equalToSuperview().inset(AppSpacing.md)
make.height.equalTo(0.5)
}
actionStack.snp.makeConstraints { make in
make.top.equalTo(dividerView.snp.bottom).offset(AppSpacing.sm)
make.leading.trailing.equalToSuperview().inset(AppSpacing.md)
make.height.equalTo(AppSpacing.minTouchTarget)
make.bottom.equalToSuperview().inset(14)
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func prepareForReuse() {
super.prepareForReuse()
onEditRemark = nil
onEditCommission = nil
onViewCommissionLogs = nil
onCall = nil
}
func configure(with acquirer: CooperativeSalerEntity) {
nameValueLabel.text = acquirer.displayName.isEmpty ? "—" : acquirer.displayName
phoneValueLabel.text = acquirer.displayPhone.isEmpty ? "—" : CooperationOrderPhoneMask.mask(acquirer.displayPhone)
let bindTime = acquirer.displayBindTime.isEmpty ? "—" : Self.minutePrecisionTime(acquirer.displayBindTime)
bindTimeValueLabel.text = "绑定于 \(bindTime)"
commissionValueLabel.text = acquirer.displayCommissionRate.isEmpty ? "—" : acquirer.displayCommissionRate
}
private func configureActionButton(
_ button: UIButton,
title: String,
isPrimary: Bool,
accessibilityIdentifier: String
) {
var configuration = UIButton.Configuration.plain()
configuration.title = title
configuration.baseForegroundColor = isPrimary ? AppColor.primary : UIColor(hex: 0x5F6673)
configuration.background.backgroundColor = isPrimary ? AppColor.primaryLight : .white
configuration.background.strokeColor = isPrimary
? AppColor.primary.withAlphaComponent(0.35)
: UIColor(hex: 0xD9DEE7)
configuration.background.strokeWidth = 1
configuration.background.cornerRadius = AppRadius.md
configuration.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 6, bottom: 0, trailing: 6)
configuration.titleTextAttributesTransformer = UIConfigurationTextAttributesTransformer { attributes in
var updated = attributes
updated.font = .systemFont(ofSize: 12, weight: .medium)
return updated
}
button.configuration = configuration
button.accessibilityIdentifier = accessibilityIdentifier
}
private static func minutePrecisionTime(_ value: String) -> String {
let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
guard trimmed.count == 19, trimmed.dropFirst(16).first == ":" else { return trimmed }
return String(trimmed.prefix(16))
}
@objc private func editRemarkTapped() { onEditRemark?() }
@objc private func editCommissionTapped() { onEditCommission?() }
@objc private func commissionLogTapped() { onViewCommissionLogs?() }
@objc private func callTapped() { onCall?() }
}
/// 修改获客员分成比例弹窗,对齐 Android `CommissionRateEditDialog`。
final class CooperationCommissionRateDialogView: KeyboardAvoidingDialogView {
var onCommissionRateChange: ((String) -> Void)?
var onSmsCodeChange: ((String) -> Void)?
var onSendSmsCode: (() -> Void)?
var onCancel: (() -> Void)?
var onConfirm: (() -> Void)?
var currentCommissionRate: String { rateField.text }
var currentSmsCode: String { smsCodeField.text }
private let dimView = UIView()
private let cardView = UIView()
private let titleLabel = UILabel()
private let infoBox = UIView()
private let infoLabel = UILabel()
private let contentStack = UIStackView()
private let rateField = CooperationOutlinedTextFieldView(title: "分成比例", placeholder: "请输入0到100", suffix: "%")
private let phoneField = CooperationOutlinedTextFieldView(title: "验证码发送手机号", placeholder: "—")
private let smsCodeField = CooperationOutlinedTextFieldView(title: "验证码", placeholder: "请输入验证码")
private let sendCodeButton = UIButton(type: .system)
private let buttonStack = UIStackView()
private let cancelButton = UIButton(type: .system)
private let confirmButton = UIButton(type: .system)
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func apply(commissionRate: String, smsCode: String, phone: String, countdown: Int) {
rateField.text = commissionRate
smsCodeField.text = smsCode
phoneField.text = phone.isEmpty ? "—" : phone
let isCountingDown = countdown > 0
sendCodeButton.setTitle(isCountingDown ? "\(countdown)秒" : "获取验证码", for: .normal)
sendCodeButton.isEnabled = !isCountingDown
sendCodeButton.backgroundColor = AppColor.primary.withAlphaComponent(isCountingDown ? 0.7 : 1)
sendCodeButton.setTitleColor(.white.withAlphaComponent(isCountingDown ? 0.7 : 1), for: .normal)
sendCodeButton.setTitleColor(.white.withAlphaComponent(0.7), for: .disabled)
}
func show(in parent: UIView) {
frame = parent.bounds
autoresizingMask = [.flexibleWidth, .flexibleHeight]
alpha = 0
parent.addSubview(self)
layoutIfNeeded()
rateField.focus()
UIView.animate(withDuration: 0.2) {
self.alpha = 1
}
}
func dismiss() {
endEditing(true)
UIView.animate(withDuration: 0.2, animations: {
self.alpha = 0
}, completion: { _ in
self.removeFromSuperview()
})
}
private func setupUI() {
keyboardAvoidingContentView = cardView
dimView.backgroundColor = AppColor.overlayScrim
dimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(cancelTapped)))
cardView.backgroundColor = .white
cardView.layer.cornerRadius = AppRadius.xl
cardView.clipsToBounds = true
titleLabel.text = "修改分成比例"
titleLabel.font = .systemFont(ofSize: 18, weight: .bold)
titleLabel.textColor = AppColor.textPrimary
infoBox.backgroundColor = AppColor.primary.withAlphaComponent(0.08)
infoBox.layer.cornerRadius = AppRadius.sm
infoLabel.text = "修改分成比例需验证手机号"
infoLabel.font = .systemFont(ofSize: 13)
infoLabel.textColor = AppColor.primary
contentStack.axis = .vertical
contentStack.spacing = AppSpacing.sm
rateField.keyboardType = .numberPad
smsCodeField.keyboardType = .numberPad
phoneField.isReadOnly = true
rateField.onTextChange = { [weak self] text in
self?.onCommissionRateChange?(text)
}
smsCodeField.onTextChange = { [weak self] text in
self?.onSmsCodeChange?(text)
}
sendCodeButton.setTitle("获取验证码", for: .normal)
sendCodeButton.titleLabel?.font = .systemFont(ofSize: 13, weight: .medium)
sendCodeButton.setTitleColor(.white, for: .normal)
sendCodeButton.backgroundColor = AppColor.primary
sendCodeButton.layer.cornerRadius = AppRadius.sm
sendCodeButton.addTarget(self, action: #selector(sendCodeTapped), for: .touchUpInside)
buttonStack.axis = .horizontal
buttonStack.alignment = .center
buttonStack.distribution = .fillEqually
buttonStack.spacing = AppSpacing.sm
cancelButton.setTitle("取消", for: .normal)
cancelButton.titleLabel?.font = .systemFont(ofSize: 15, weight: .medium)
cancelButton.setTitleColor(AppColor.textSecondary, for: .normal)
cancelButton.addTarget(self, action: #selector(cancelTapped), for: .touchUpInside)
confirmButton.setTitle("确认修改", for: .normal)
confirmButton.titleLabel?.font = .systemFont(ofSize: 15, weight: .medium)
confirmButton.setTitleColor(AppColor.primary, for: .normal)
confirmButton.addTarget(self, action: #selector(confirmTapped), for: .touchUpInside)
addSubview(dimView)
addSubview(cardView)
cardView.addSubview(titleLabel)
cardView.addSubview(infoBox)
infoBox.addSubview(infoLabel)
cardView.addSubview(contentStack)
contentStack.addArrangedSubview(rateField)
contentStack.addArrangedSubview(phoneField)
contentStack.addArrangedSubview(makeSmsRow())
cardView.addSubview(buttonStack)
buttonStack.addArrangedSubview(cancelButton)
buttonStack.addArrangedSubview(confirmButton)
dimView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
cardView.snp.makeConstraints { make in
make.leading.trailing.equalToSuperview().inset(AppSpacing.md)
keyboardAvoidingCenterYConstraint = make.centerY.equalToSuperview().constraint
}
titleLabel.snp.makeConstraints { make in
make.top.leading.trailing.equalToSuperview().inset(AppSpacing.md)
}
infoBox.snp.makeConstraints { make in
make.top.equalTo(titleLabel.snp.bottom).offset(AppSpacing.md)
make.leading.trailing.equalToSuperview().inset(AppSpacing.md)
}
infoLabel.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(UIEdgeInsets(top: 10, left: 12, bottom: 10, right: 12))
}
contentStack.snp.makeConstraints { make in
make.top.equalTo(infoBox.snp.bottom).offset(AppSpacing.sm)
make.leading.trailing.equalToSuperview().inset(AppSpacing.md)
}
buttonStack.snp.makeConstraints { make in
make.top.equalTo(contentStack.snp.bottom).offset(AppSpacing.md)
make.leading.trailing.bottom.equalToSuperview().inset(AppSpacing.md)
make.height.equalTo(44)
}
}
private func makeSmsRow() -> UIView {
let row = UIStackView(arrangedSubviews: [smsCodeField, sendCodeButton])
row.axis = .horizontal
row.alignment = .fill
row.spacing = 10
sendCodeButton.snp.makeConstraints { make in
make.width.equalTo(104)
make.height.equalTo(46)
}
return row
}
@objc private func sendCodeTapped() {
onSendSmsCode?()
}
@objc private func cancelTapped() {
onCancel?()
}
@objc private func confirmTapped() {
onConfirm?()
}
}
/// 合作订单弹窗内的描边输入框。
private final class CooperationOutlinedTextFieldView: UIView {
var onTextChange: ((String) -> Void)?
var text: String {
get { textField.text ?? "" }
set {
if textField.text != newValue {
textField.text = newValue
}
}
}
var keyboardType: UIKeyboardType {
get { textField.keyboardType }
set { textField.keyboardType = newValue }
}
var isReadOnly = false {
didSet {
textField.isUserInteractionEnabled = !isReadOnly
textField.textColor = isReadOnly ? AppColor.textSecondary : AppColor.textPrimary
}
}
private let titleLabel = UILabel()
private let containerView = UIView()
private let textField = UITextField()
private let suffixLabel = UILabel()
private let suffix: String?
init(title: String, placeholder: String, suffix: String? = nil) {
self.suffix = suffix
super.init(frame: .zero)
setupUI(title: title, placeholder: placeholder)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func focus() {
textField.becomeFirstResponder()
}
private func setupUI(title: String, placeholder: String) {
titleLabel.text = title
titleLabel.font = .systemFont(ofSize: 12)
titleLabel.textColor = AppColor.textSecondary
titleLabel.backgroundColor = .white
containerView.layer.borderWidth = 1
containerView.layer.borderColor = UIColor(hex: 0xD9DDE4).cgColor
containerView.layer.cornerRadius = AppRadius.sm
textField.placeholder = placeholder
textField.font = .systemFont(ofSize: 15)
textField.textColor = AppColor.textPrimary
textField.borderStyle = .none
textField.backgroundColor = .clear
textField.addTarget(self, action: #selector(textChanged), for: .editingChanged)
suffixLabel.text = suffix
suffixLabel.font = .systemFont(ofSize: 15)
suffixLabel.textColor = AppColor.textSecondary
suffixLabel.isHidden = suffix == nil
addSubview(containerView)
addSubview(titleLabel)
containerView.addSubview(textField)
containerView.addSubview(suffixLabel)
snp.makeConstraints { make in
make.height.equalTo(56)
}
containerView.snp.makeConstraints { make in
make.leading.trailing.bottom.equalToSuperview()
make.top.equalToSuperview().offset(6)
}
titleLabel.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(10)
make.top.equalToSuperview()
}
textField.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(AppSpacing.sm)
make.trailing.equalTo(suffixLabel.snp.leading).offset(-AppSpacing.xs)
make.centerY.equalTo(containerView)
}
suffixLabel.snp.makeConstraints { make in
make.trailing.equalToSuperview().inset(AppSpacing.sm)
make.centerY.equalTo(containerView)
}
}
@objc private func textChanged() {
onTextChange?(textField.text ?? "")
}
}
/// 标签-值行。
final class CooperationInfoRowView: UIView {
private let titleLabel = UILabel()
private let valueLabel = UILabel()
init(label: String) {
super.init(frame: .zero)
titleLabel.text = label
titleLabel.font = .systemFont(ofSize: 13)
titleLabel.textColor = AppColor.textTertiary
valueLabel.font = .systemFont(ofSize: 13)
valueLabel.textColor = AppColor.textSecondary
valueLabel.numberOfLines = 0
addSubview(titleLabel)
addSubview(valueLabel)
titleLabel.snp.makeConstraints { make in
make.leading.top.bottom.equalToSuperview()
make.width.equalTo(72)
}
valueLabel.snp.makeConstraints { make in
make.leading.equalTo(titleLabel.snp.trailing).offset(12)
make.trailing.top.bottom.equalToSuperview()
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setValue(_ value: String) {
valueLabel.text = value
}
}
enum CooperationOrderPhoneMask {
static func mask(_ phone: String) -> String {
let digits = phone.filter(\.isNumber)
guard digits.count >= 7 else { return phone }
return "\(digits.prefix(3))****\(digits.suffix(4))"
}
}