1044 lines
41 KiB
Swift
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))"
|
||
}
|
||
}
|