Files
suixinkan_uikit/suixinkan/UI/Invite/PhotographerInviteViewController.swift
汉秋 6336feff27 完善景区排队设置页与全局按钮配置迁移。
重构排队设置与变更日志交互,补充 Overlay 与资源,并将多页面主操作按钮统一到 UIButton Configuration,同步更新相关单元测试。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-14 16:21:53 +08:00

448 lines
16 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters

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

//
// PhotographerInviteViewController.swift
// suixinkan
//
import SnapKit
import UIKit
///
final class PhotographerInviteViewController: BaseViewController {
private let viewModel = PhotographerInviteViewModel()
private let inviteAPI: InviteServing
private let scrollView = UIScrollView()
private let contentStack = UIStackView()
private let qrCard = UIView()
private let qrImageView = UIImageView()
private let inviteCodeLabel = UILabel()
private let ruleStack = UIStackView()
///
init(inviteAPI: InviteServing? = nil) {
self.inviteAPI = inviteAPI ?? NetworkServices.shared.inviteAPI
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func setupNavigationBar() {
title = "摄影师邀请"
}
override func setupUI() {
view.backgroundColor = UIColor(hex: 0xF5F6F8)
scrollView.alwaysBounceVertical = true
scrollView.refreshControl = UIRefreshControl()
contentStack.axis = .vertical
contentStack.spacing = 16
view.addSubview(scrollView)
scrollView.addSubview(contentStack)
contentStack.addArrangedSubview(makeInviteCard())
contentStack.addArrangedSubview(makeRuleCard())
contentStack.addArrangedSubview(makeBenefitCard())
contentStack.addArrangedSubview(makeInviteRecordEntry())
}
override func setupConstraints() {
scrollView.snp.makeConstraints { make in
make.edges.equalTo(view.safeAreaLayoutGuide)
}
contentStack.snp.makeConstraints { make in
make.top.bottom.equalTo(scrollView.contentLayoutGuide).inset(16)
make.leading.trailing.equalTo(scrollView.frameLayoutGuide).inset(16)
make.width.equalTo(scrollView.frameLayoutGuide).offset(-32)
}
}
override func bindActions() {
scrollView.refreshControl?.addTarget(self, action: #selector(refreshPulled), for: .valueChanged)
viewModel.onStateChange = { [weak self] in
Task { @MainActor in
self?.applyViewModel()
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
Task { await viewModel.reload(api: inviteAPI) }
}
private func makeInviteCard() -> UIView {
qrCard.backgroundColor = .white
qrCard.layer.cornerRadius = 12
qrCard.clipsToBounds = true
let stack = UIStackView()
stack.axis = .vertical
stack.spacing = 16
stack.alignment = .fill
qrCard.addSubview(stack)
qrImageView.contentMode = .scaleAspectFit
qrImageView.backgroundColor = .white
qrImageView.image = UIImage(systemName: "qrcode")
qrImageView.tintColor = AppColor.textTertiary
stack.addArrangedSubview(qrImageView)
let codeRow = UIStackView()
codeRow.axis = .horizontal
codeRow.alignment = .center
codeRow.spacing = 8
inviteCodeLabel.font = .systemFont(ofSize: 16)
inviteCodeLabel.textColor = UIColor(hex: 0x4B5563)
inviteCodeLabel.numberOfLines = 1
codeRow.addArrangedSubview(inviteCodeLabel)
let copyButton = UIButton(type: .system)
copyButton.setTitle("复制", for: .normal)
copyButton.setTitleColor(.white, for: .normal)
copyButton.titleLabel?.font = .systemFont(ofSize: 14, weight: .medium)
copyButton.backgroundColor = AppColor.primary
copyButton.layer.cornerRadius = 6
copyButton.addTarget(self, action: #selector(copyCodeTapped), for: .touchUpInside)
codeRow.addArrangedSubview(copyButton)
copyButton.snp.makeConstraints { make in
make.width.equalTo(64)
make.height.equalTo(28)
}
stack.addArrangedSubview(codeRow)
let actionRow = UIStackView()
actionRow.axis = .horizontal
actionRow.spacing = 12
actionRow.distribution = .fillEqually
let shareButton = InviteIconButton(title: "分享", imageName: "ic_invite_share", backgroundColor: UIColor(hex: 0x07C160))
shareButton.addTarget(self, action: #selector(shareTapped), for: .touchUpInside)
let linkButton = InviteIconButton(title: "复制链接", imageName: "ic_invite_link", backgroundColor: AppColor.primary)
linkButton.addTarget(self, action: #selector(copyLinkTapped), for: .touchUpInside)
actionRow.addArrangedSubview(shareButton)
actionRow.addArrangedSubview(linkButton)
stack.addArrangedSubview(actionRow)
actionRow.snp.makeConstraints { make in
make.height.equalTo(44)
}
stack.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(16)
}
qrImageView.snp.makeConstraints { make in
make.height.equalTo(280)
}
return qrCard
}
private func makeRuleCard() -> UIView {
let card = UIView()
card.backgroundColor = .white
card.layer.cornerRadius = 12
card.layer.borderWidth = 1
card.layer.borderColor = AppColor.cardOutline.cgColor
let titleLabel = UILabel()
titleLabel.text = "收益规则"
titleLabel.font = .systemFont(ofSize: 18, weight: .medium)
titleLabel.textColor = .black
ruleStack.axis = .vertical
ruleStack.spacing = 12
let stack = UIStackView(arrangedSubviews: [titleLabel, ruleStack])
stack.axis = .vertical
stack.spacing = 12
card.addSubview(stack)
stack.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(16)
}
return card
}
private func makeBenefitCard() -> UIView {
let card = UIView()
card.backgroundColor = .white
card.layer.cornerRadius = 12
card.clipsToBounds = true
let titleLabel = UILabel()
titleLabel.text = "新人福利"
titleLabel.font = .systemFont(ofSize: 18, weight: .medium)
titleLabel.textColor = .black
let grid = UIStackView()
grid.axis = .vertical
grid.spacing = 12
let benefits: [(String, String)] = [
("免费剪辑修图", "ic_invite_benefit_free_editing"),
("免费云盘相册", "ic_invite_benefit_free_cloud"),
("免费线上流量", "ic_free_traffic"),
("景区运营身份", "ic_scenic_identity"),
("接单软件工具", "ic_order_tools"),
("免佣金特权", "ic_no_commission"),
]
for index in stride(from: 0, to: benefits.count, by: 2) {
let row = UIStackView()
row.axis = .horizontal
row.spacing = 12
row.distribution = .fillEqually
row.addArrangedSubview(InviteBenefitView(title: benefits[index].0, imageName: benefits[index].1))
row.addArrangedSubview(InviteBenefitView(title: benefits[index + 1].0, imageName: benefits[index + 1].1))
grid.addArrangedSubview(row)
}
let validityStack = UIStackView()
validityStack.axis = .vertical
validityStack.spacing = 8
["活动有效期: 2026年9月1日", "佣金有效期: 2028年1月1日"].forEach { text in
let label = UILabel()
label.text = text
label.font = .app(.body)
label.textColor = UIColor(hex: 0x7B8EAA)
validityStack.addArrangedSubview(label)
}
let stack = UIStackView(arrangedSubviews: [titleLabel, grid, validityStack])
stack.axis = .vertical
stack.spacing = 16
card.addSubview(stack)
stack.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(16)
}
return card
}
private func makeInviteRecordEntry() -> UIView {
let container = UIView()
container.backgroundColor = UIColor(hex: 0xF5F6F8)
container.snp.makeConstraints { make in
make.height.equalTo(64)
}
let button = UIButton(type: .system)
button.backgroundColor = .white
button.layer.cornerRadius = 8
button.setTitle("邀请记录", for: .normal)
button.setTitleColor(AppColor.primary, for: .normal)
button.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
button.setImage(UIImage(named: "ic_invite_record")?.withRenderingMode(.alwaysTemplate), for: .normal)
button.tintColor = AppColor.primary
button.setConfigurationImagePlacement(.leading, padding: 6)
button.addTarget(self, action: #selector(inviteRecordTapped), for: .touchUpInside)
container.addSubview(button)
button.snp.makeConstraints { make in
make.leading.trailing.equalToSuperview()
make.top.equalToSuperview().offset(16)
make.height.equalTo(48)
}
return container
}
@MainActor
private func applyViewModel() {
if viewModel.isLoading {
showLoading()
} else {
hideLoading()
scrollView.refreshControl?.endRefreshing()
}
if let qrImage = viewModel.qrImage {
qrImageView.image = qrImage
}
let attributed = NSMutableAttributedString(
string: "邀请码: ",
attributes: [.foregroundColor: UIColor(hex: 0x4B5563)]
)
attributed.append(
NSAttributedString(
string: viewModel.inviteCode.isEmpty ? "--" : viewModel.inviteCode,
attributes: [.foregroundColor: AppColor.primary]
)
)
inviteCodeLabel.attributedText = attributed
applyRules()
if let message = viewModel.errorMessage {
showToast("获取邀请信息失败: \(message)")
}
}
private func applyRules() {
ruleStack.arrangedSubviews.forEach { view in
ruleStack.removeArrangedSubview(view)
view.removeFromSuperview()
}
viewModel.rules.enumerated().forEach { index, rule in
ruleStack.addArrangedSubview(InviteRuleRow(rule: rule, imageName: index.isMultiple(of: 2) ? "ic_invite_earnings" : "ic_user_benefits"))
}
}
@objc private func refreshPulled() {
Task { await viewModel.reload(api: inviteAPI) }
}
@objc private func copyCodeTapped() {
guard !viewModel.inviteCodeForCopy().isEmpty else {
showToast("邀请码加载中,请稍后重试")
return
}
UIPasteboard.general.string = viewModel.inviteCodeForCopy()
showToast("已复制成功")
}
@objc private func copyLinkTapped() {
guard !viewModel.inviteUrlForCopy().isEmpty else {
showToast("邀请链接加载中,请稍后重试")
return
}
UIPasteboard.general.string = viewModel.inviteUrlForCopy()
showToast("已复制成功")
}
@objc private func shareTapped() {
guard !viewModel.inviteUrl.isEmpty else {
showToast("邀请链接加载中,请稍后重试")
return
}
let payload = WeChatWebPageSharePayload(
title: "邀请你加入随心瞰商家版",
description: viewModel.inviteCode.isEmpty ? "摄影师邀请" : "邀请码:\(viewModel.inviteCode)",
url: viewModel.inviteUrl
)
WeChatShareService.shareWebPage(payload, scene: .session, thumbImage: viewModel.qrImage ?? WeChatShareService.defaultThumbImage()) { [weak self] result in
Task { @MainActor in
self?.handleShareResult(result)
}
}
}
@objc private func inviteRecordTapped() {
navigationController?.pushViewController(InviteRecordViewController(), animated: true)
}
private func handleShareResult(_ result: WeChatShareResult) {
switch result {
case .success:
showToast("已分享到微信好友")
case .cancelled:
showToast("已取消分享")
case .notInstalled:
showToast("未安装微信,无法分享")
case .unsupported:
showToast("当前微信版本不支持分享")
case .invalidPayload(let message):
showToast(message)
case .sendFailed, .unknown:
showToast("分享失败,请稍后重试")
}
}
}
///
private final class InviteIconButton: UIButton {
///
init(title: String, imageName: String, backgroundColor: UIColor) {
super.init(frame: .zero)
self.backgroundColor = backgroundColor
layer.cornerRadius = 8
setTitle(title, for: .normal)
setTitleColor(.white, for: .normal)
setImage(UIImage(named: imageName)?.withRenderingMode(.alwaysTemplate), for: .normal)
tintColor = .white
titleLabel?.font = .systemFont(ofSize: 14, weight: .medium)
setConfigurationImagePlacement(.leading, padding: 6)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
///
private final class InviteRuleRow: UIView {
///
init(rule: String, imageName: String) {
super.init(frame: .zero)
let iconView = UIImageView(image: UIImage(named: imageName)?.withRenderingMode(.alwaysTemplate))
iconView.tintColor = AppColor.primary
iconView.contentMode = .scaleAspectFit
let label = UILabel()
label.text = rule
label.font = .app(.body)
label.textColor = .black
label.numberOfLines = 0
addSubview(iconView)
addSubview(label)
iconView.snp.makeConstraints { make in
make.leading.equalToSuperview()
make.top.equalToSuperview().offset(2)
make.size.equalTo(16)
}
label.snp.makeConstraints { make in
make.top.bottom.trailing.equalToSuperview()
make.leading.equalTo(iconView.snp.trailing).offset(12)
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
///
private final class InviteBenefitView: UIView {
///
init(title: String, imageName: String) {
super.init(frame: .zero)
backgroundColor = UIColor(hex: 0xEFF6FF)
layer.cornerRadius = 12
let iconView = UIImageView(image: UIImage(named: imageName)?.withRenderingMode(.alwaysTemplate))
iconView.tintColor = AppColor.primary
iconView.contentMode = .scaleAspectFit
let titleLabel = UILabel()
titleLabel.text = title
titleLabel.font = .app(.body)
titleLabel.textColor = .black
titleLabel.textAlignment = .center
titleLabel.numberOfLines = 2
addSubview(iconView)
addSubview(titleLabel)
snp.makeConstraints { make in
make.height.equalTo(80)
}
iconView.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalToSuperview().offset(12)
make.size.equalTo(24)
}
titleLabel.snp.makeConstraints { make in
make.leading.trailing.equalToSuperview().inset(8)
make.top.equalTo(iconView.snp.bottom).offset(8)
make.bottom.lessThanOrEqualToSuperview().inset(8)
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}