// // 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.. 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))" } }