新增线下收款记录和 ai 修图优化

This commit is contained in:
han xin
2026-08-21 15:51:52 +08:00
parent 6fe9928b49
commit 5cf4409bad
40 changed files with 5482 additions and 56 deletions
@@ -0,0 +1,761 @@
//
// AccountDeletionViewControllers.swift
// suixinkan
//
import SnapKit
import UIKit
/// 注销账号资产核验页,中性展示账号资产与注销后果。
final class AccountDeletionViewController: BaseViewController {
private let viewModel: AccountDeletionViewModel
private let scrollView = UIScrollView()
private let contentView = UIView()
private let contentStack = UIStackView()
private let warningView = AccountDeletionWarningView()
private let assetCardView = AccountDeletionAssetCardView()
private let detailButton = UIButton(type: .system)
private let consequenceCardView = AccountDeletionConsequenceCardView()
private let bottomPanel = UIView()
private let acknowledgementButton = UIButton(type: .system)
private let continueButton = AppButton(title: "继续注销", style: .primary)
private var lastErrorMessage: String?
/// 创建注销资产核验页。
init(viewModel: AccountDeletionViewModel = AccountDeletionViewModel()) {
self.viewModel = viewModel
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 = AppColor.pageBackground
scrollView.alwaysBounceVertical = true
scrollView.showsVerticalScrollIndicator = false
contentStack.axis = .vertical
contentStack.spacing = 16
contentStack.isLayoutMarginsRelativeArrangement = true
contentStack.layoutMargins = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)
detailButton.setTitle("查看资产明细", for: .normal)
detailButton.setTitleColor(AppColor.primary, for: .normal)
detailButton.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
bottomPanel.backgroundColor = AppColor.pageBackground
acknowledgementButton.contentHorizontalAlignment = .leading
acknowledgementButton.tintColor = AppColor.primary
acknowledgementButton.setTitleColor(AppColor.textPrimary, for: .normal)
acknowledgementButton.titleLabel?.font = .systemFont(ofSize: 14)
acknowledgementButton.titleLabel?.numberOfLines = 0
acknowledgementButton.setTitle(" 我已知晓并自愿放弃以上资产和权益", for: .normal)
acknowledgementButton.accessibilityLabel = "我已知晓并自愿放弃以上资产和权益"
view.addSubview(scrollView)
scrollView.addSubview(contentView)
contentView.addSubview(contentStack)
view.addSubview(bottomPanel)
bottomPanel.addSubview(acknowledgementButton)
bottomPanel.addSubview(continueButton)
[warningView, assetCardView, detailButton, consequenceCardView].forEach(contentStack.addArrangedSubview)
applyViewModel()
}
override func setupConstraints() {
bottomPanel.snp.makeConstraints { make in
make.leading.trailing.bottom.equalToSuperview()
}
acknowledgementButton.snp.makeConstraints { make in
make.top.equalToSuperview().offset(8)
make.leading.trailing.equalToSuperview().inset(16)
make.height.greaterThanOrEqualTo(44)
}
continueButton.snp.makeConstraints { make in
make.top.equalTo(acknowledgementButton.snp.bottom).offset(8)
make.leading.trailing.equalToSuperview().inset(16)
make.bottom.equalTo(view.safeAreaLayoutGuide).offset(-12)
}
scrollView.snp.makeConstraints { make in
make.top.leading.trailing.equalTo(view.safeAreaLayoutGuide)
make.bottom.equalTo(bottomPanel.snp.top)
}
contentView.snp.makeConstraints { make in
make.edges.equalToSuperview()
make.width.equalTo(scrollView.snp.width)
}
contentStack.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
warningView.snp.makeConstraints { make in
make.height.greaterThanOrEqualTo(64)
}
detailButton.snp.makeConstraints { make in
make.height.equalTo(36)
}
}
override func bindActions() {
viewModel.onStateChange = { [weak self] in
Task { @MainActor in self?.applyViewModel() }
}
acknowledgementButton.addTarget(self, action: #selector(acknowledgementTapped), for: .touchUpInside)
continueButton.addTarget(self, action: #selector(continueTapped), for: .touchUpInside)
detailButton.addTarget(self, action: #selector(detailTapped), for: .touchUpInside)
}
override func viewDidLoad() {
super.viewDidLoad()
Task {
showLoading()
await viewModel.load()
hideLoading()
}
}
private func applyViewModel() {
assetCardView.configure(assets: viewModel.assets)
consequenceCardView.configure(items: viewModel.consequences)
acknowledgementButton.setImage(
UIImage(systemName: viewModel.isAcknowledged ? "checkmark.square.fill" : "square"),
for: .normal
)
acknowledgementButton.accessibilityValue = viewModel.isAcknowledged ? "已勾选" : "未勾选"
continueButton.isEnabled = viewModel.isContinueButtonEnabled
if let errorMessage = viewModel.errorMessage,
!errorMessage.isEmpty,
errorMessage != lastErrorMessage {
lastErrorMessage = errorMessage
showToast(errorMessage)
}
}
@objc private func acknowledgementTapped() {
viewModel.toggleAcknowledgement()
}
@objc private func continueTapped() {
do {
let verificationViewModel = try viewModel.makeVerificationViewModel()
navigationController?.pushViewController(
AccountDeletionVerificationViewController(viewModel: verificationViewModel),
animated: true
)
} catch {
showToast(error.localizedDescription)
}
}
@objc private func detailTapped() {
let message = viewModel.assets
.map { "\($0.title):\($0.valueText)" }
.joined(separator: "\n")
showAlert(title: "当前账号资产", message: message)
}
}
/// 注销手机号验证页,使用短信验证码确认是账号本人操作。
final class AccountDeletionVerificationViewController: BaseViewController {
private let viewModel: AccountDeletionVerificationViewModel
private let contentStack = UIStackView()
private let iconView = UIImageView()
private let titleLabel = UILabel()
private let descriptionLabel = UILabel()
private let codeContainer = UIView()
private let codeField = UITextField()
private let resendButton = UIButton(type: .system)
private let demoHintLabel = UILabel()
private let noticeView = AccountDeletionNoticeView()
private let submitButton = AppButton(title: "提交注销申请", style: .primary)
private var lastErrorMessage: String?
/// 创建注销手机号验证页。
init(viewModel: AccountDeletionVerificationViewModel) {
self.viewModel = viewModel
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 = AppColor.pageBackground
contentStack.axis = .vertical
contentStack.alignment = .fill
contentStack.spacing = 16
iconView.image = UIImage(systemName: "message.badge.filled.fill")
iconView.tintColor = AppColor.primary
iconView.contentMode = .scaleAspectFit
titleLabel.text = "确认是你本人操作"
titleLabel.font = .systemFont(ofSize: 22, weight: .semibold)
titleLabel.textColor = AppColor.textPrimary
titleLabel.textAlignment = .center
descriptionLabel.text = "验证码将发送至 \(viewModel.context.maskedPhone)"
descriptionLabel.font = .systemFont(ofSize: 15)
descriptionLabel.textColor = AppColor.textSecondary
descriptionLabel.textAlignment = .center
codeContainer.backgroundColor = .white
codeContainer.layer.cornerRadius = 12
codeContainer.layer.borderWidth = 1
codeContainer.layer.borderColor = AppColor.border.cgColor
codeField.placeholder = "请输入6位验证码"
codeField.font = .systemFont(ofSize: 18, weight: .medium)
codeField.textColor = AppColor.textPrimary
codeField.keyboardType = .numberPad
codeField.textContentType = .oneTimeCode
codeField.clearButtonMode = .whileEditing
resendButton.setTitle("重新发送", for: .normal)
resendButton.setTitleColor(AppColor.primary, for: .normal)
resendButton.titleLabel?.font = .systemFont(ofSize: 14, weight: .medium)
demoHintLabel.text = "演示验证码:123456"
demoHintLabel.font = .systemFont(ofSize: 13)
demoHintLabel.textColor = AppColor.warning
demoHintLabel.textAlignment = .center
noticeView.configure(
iconName: "calendar.badge.exclamationmark",
text: "提交成功后账号将退出,7天内再次登录可取消注销。"
)
view.addSubview(contentStack)
view.addSubview(submitButton)
codeContainer.addSubview(codeField)
codeContainer.addSubview(resendButton)
[iconView, titleLabel, descriptionLabel, codeContainer, demoHintLabel, noticeView]
.forEach(contentStack.addArrangedSubview)
applyViewModel()
}
override func setupConstraints() {
contentStack.snp.makeConstraints { make in
make.top.equalTo(view.safeAreaLayoutGuide).offset(32)
make.leading.trailing.equalToSuperview().inset(24)
}
iconView.snp.makeConstraints { make in
make.height.equalTo(64)
}
codeContainer.snp.makeConstraints { make in
make.height.equalTo(56)
}
codeField.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(16)
make.top.bottom.equalToSuperview()
make.trailing.equalTo(resendButton.snp.leading).offset(-8)
}
resendButton.snp.makeConstraints { make in
make.trailing.equalToSuperview().offset(-12)
make.centerY.equalToSuperview()
make.width.equalTo(76)
make.height.equalTo(44)
}
noticeView.snp.makeConstraints { make in
make.height.greaterThanOrEqualTo(64)
}
submitButton.snp.makeConstraints { make in
make.leading.trailing.equalToSuperview().inset(16)
make.bottom.equalTo(view.safeAreaLayoutGuide).offset(-12)
}
}
override func bindActions() {
viewModel.onStateChange = { [weak self] in
Task { @MainActor in self?.applyViewModel() }
}
codeField.addTarget(self, action: #selector(codeChanged), for: .editingChanged)
resendButton.addTarget(self, action: #selector(resendTapped), for: .touchUpInside)
submitButton.addTarget(self, action: #selector(submitTapped), for: .touchUpInside)
}
override func viewDidLoad() {
super.viewDidLoad()
Task {
await viewModel.sendVerificationCode()
if viewModel.hasSentCode {
showToast("验证码已发送")
}
}
}
private func applyViewModel() {
if codeField.text != viewModel.verificationCode {
codeField.text = viewModel.verificationCode
}
resendButton.isEnabled = !viewModel.isSendingCode && !viewModel.isSubmitting
resendButton.setTitle(viewModel.isSendingCode ? "发送中" : "重新发送", for: .normal)
submitButton.isEnabled = viewModel.isSubmitEnabled
if let errorMessage = viewModel.errorMessage,
!errorMessage.isEmpty,
errorMessage != lastErrorMessage {
lastErrorMessage = errorMessage
showToast(errorMessage)
}
}
@objc private func codeChanged() {
viewModel.updateVerificationCode(codeField.text ?? "")
}
@objc private func resendTapped() {
Task {
await viewModel.sendVerificationCode()
if viewModel.hasSentCode {
showToast("验证码已重新发送")
}
}
}
@objc private func submitTapped() {
let alert = UIAlertController(
title: "确认提交注销申请?",
message: "提交后账号将退出,7天内再次登录可以取消注销。",
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "再想想", style: .cancel))
alert.addAction(UIAlertAction(title: "确认提交", style: .destructive) { [weak self] _ in
self?.submitDeletion()
})
present(alert, animated: true)
}
private func submitDeletion() {
Task {
showLoading()
defer { hideLoading() }
do {
let request = try await viewModel.submit()
navigationController?.pushViewController(
AccountDeletionSuccessViewController(request: request),
animated: true
)
} catch is CancellationError {
return
} catch {
showToast(error.localizedDescription)
}
}
}
}
/// 注销申请提交成功页,明确展示计划删除时间和恢复方式。
final class AccountDeletionSuccessViewController: BaseViewController {
private let request: AccountDeletionRequest
private let iconView = UIImageView()
private let titleLabel = UILabel()
private let descriptionLabel = UILabel()
private let dateCardView = UIView()
private let dateTitleLabel = UILabel()
private let dateValueLabel = UILabel()
private let recoveryLabel = UILabel()
private let exitButton = AppButton(title: "退出并返回登录页", style: .primary)
/// 创建注销申请提交成功页。
init(request: AccountDeletionRequest) {
self.request = request
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func setupNavigationBar() {
title = "注销账号"
navigationItem.hidesBackButton = true
}
override func setupUI() {
view.backgroundColor = AppColor.pageBackground
iconView.image = UIImage(systemName: "checkmark.circle.fill")
iconView.tintColor = AppColor.success
iconView.contentMode = .scaleAspectFit
titleLabel.text = "注销申请已提交"
titleLabel.font = .systemFont(ofSize: 24, weight: .semibold)
titleLabel.textColor = AppColor.textPrimary
titleLabel.textAlignment = .center
descriptionLabel.text = "账号已进入7天注销冷静期"
descriptionLabel.font = .systemFont(ofSize: 15)
descriptionLabel.textColor = AppColor.textSecondary
descriptionLabel.textAlignment = .center
dateCardView.backgroundColor = .white
dateCardView.layer.cornerRadius = 12
dateTitleLabel.text = "计划永久注销时间"
dateTitleLabel.font = .systemFont(ofSize: 14)
dateTitleLabel.textColor = AppColor.textSecondary
dateValueLabel.text = AccountDeletionDateFormatter.displayText(request.scheduledDeletionAt)
dateValueLabel.font = .systemFont(ofSize: 20, weight: .semibold)
dateValueLabel.textColor = AppColor.danger
dateValueLabel.textAlignment = .right
recoveryLabel.text = "在上述时间前再次使用当前手机号和密码登录,确认恢复账号后即可取消注销。"
recoveryLabel.font = .systemFont(ofSize: 15)
recoveryLabel.textColor = AppColor.textSecondary
recoveryLabel.numberOfLines = 0
recoveryLabel.textAlignment = .center
[iconView, titleLabel, descriptionLabel, dateCardView, recoveryLabel, exitButton].forEach(view.addSubview)
dateCardView.addSubview(dateTitleLabel)
dateCardView.addSubview(dateValueLabel)
}
override func setupConstraints() {
iconView.snp.makeConstraints { make in
make.top.equalTo(view.safeAreaLayoutGuide).offset(56)
make.centerX.equalToSuperview()
make.width.height.equalTo(72)
}
titleLabel.snp.makeConstraints { make in
make.top.equalTo(iconView.snp.bottom).offset(24)
make.leading.trailing.equalToSuperview().inset(24)
}
descriptionLabel.snp.makeConstraints { make in
make.top.equalTo(titleLabel.snp.bottom).offset(10)
make.leading.trailing.equalToSuperview().inset(24)
}
dateCardView.snp.makeConstraints { make in
make.top.equalTo(descriptionLabel.snp.bottom).offset(32)
make.leading.trailing.equalToSuperview().inset(16)
make.height.equalTo(72)
}
dateTitleLabel.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(16)
make.centerY.equalToSuperview()
}
dateValueLabel.snp.makeConstraints { make in
make.trailing.equalToSuperview().offset(-16)
make.centerY.equalToSuperview()
make.leading.greaterThanOrEqualTo(dateTitleLabel.snp.trailing).offset(12)
}
recoveryLabel.snp.makeConstraints { make in
make.top.equalTo(dateCardView.snp.bottom).offset(24)
make.leading.trailing.equalToSuperview().inset(32)
}
exitButton.snp.makeConstraints { make in
make.leading.trailing.equalToSuperview().inset(16)
make.bottom.equalTo(view.safeAreaLayoutGuide).offset(-12)
}
}
override func bindActions() {
exitButton.addTarget(self, action: #selector(exitTapped), for: .touchUpInside)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
navigationController?.interactivePopGestureRecognizer?.isEnabled = false
}
@objc private func exitTapped() {
NotificationCenter.default.post(name: NotificationName.userDidLogout, object: nil)
}
}
/// 注销页顶部风险提示条。
private final class AccountDeletionWarningView: UIView {
private let iconView = UIImageView()
private let label = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = AppColor.dangerBackground
layer.cornerRadius = 12
layer.borderWidth = 1
layer.borderColor = AppColor.danger.withAlphaComponent(0.25).cgColor
iconView.image = UIImage(systemName: "exclamationmark.shield.fill")
iconView.tintColor = AppColor.danger
iconView.contentMode = .scaleAspectFit
label.text = "注销后,账号及相关数据将永久删除"
label.font = .systemFont(ofSize: 15, weight: .medium)
label.textColor = AppColor.danger
label.numberOfLines = 0
addSubview(iconView)
addSubview(label)
iconView.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(16)
make.centerY.equalToSuperview()
make.width.height.equalTo(28)
}
label.snp.makeConstraints { make in
make.leading.equalTo(iconView.snp.trailing).offset(12)
make.trailing.equalToSuperview().offset(-16)
make.top.bottom.equalToSuperview().inset(14)
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
/// 注销资产汇总卡片,以列表形式中性展示名称和数量。
private final class AccountDeletionAssetCardView: UIView {
private let stackView = UIStackView()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .white
layer.cornerRadius = 12
clipsToBounds = true
stackView.axis = .vertical
addSubview(stackView)
stackView.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16))
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
/// 使用最新资产快照刷新卡片。
func configure(assets: [AccountDeletionAssetSummary]) {
stackView.arrangedSubviews.forEach {
stackView.removeArrangedSubview($0)
$0.removeFromSuperview()
}
assets.enumerated().forEach { index, asset in
stackView.addArrangedSubview(
AccountDeletionAssetRowView(asset: asset, showsDivider: index < assets.count - 1)
)
}
}
}
/// 注销资产列表行,仅显示客观资产名称和当前值。
private final class AccountDeletionAssetRowView: UIView {
private let titleLabel = UILabel()
private let valueLabel = UILabel()
private let divider = UIView()
init(asset: AccountDeletionAssetSummary, showsDivider: Bool) {
super.init(frame: .zero)
titleLabel.text = asset.title
titleLabel.font = .systemFont(ofSize: 17, weight: .medium)
titleLabel.textColor = AppColor.textPrimary
valueLabel.text = asset.valueText
valueLabel.font = .systemFont(ofSize: 18, weight: .semibold)
valueLabel.textColor = AppColor.textPrimary
valueLabel.textAlignment = .right
divider.backgroundColor = AppColor.border
divider.isHidden = !showsDivider
[titleLabel, valueLabel, divider].forEach(addSubview)
snp.makeConstraints { make in
make.height.equalTo(76)
}
titleLabel.snp.makeConstraints { make in
make.leading.centerY.equalToSuperview()
}
valueLabel.snp.makeConstraints { make in
make.trailing.centerY.equalToSuperview()
make.leading.greaterThanOrEqualTo(titleLabel.snp.trailing).offset(12)
}
divider.snp.makeConstraints { make in
make.leading.trailing.bottom.equalToSuperview()
make.height.equalTo(0.5)
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
/// 注销影响说明卡片,展示账号解除、内容删除和7天恢复规则。
private final class AccountDeletionConsequenceCardView: UIView {
private let titleLabel = UILabel()
private let stackView = UIStackView()
private let iconNames = [
"person.crop.circle.badge.xmark",
"folder",
"calendar.badge.exclamationmark",
]
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .white
layer.cornerRadius = 12
clipsToBounds = true
titleLabel.text = "注销后将发生"
titleLabel.font = .systemFont(ofSize: 18, weight: .semibold)
titleLabel.textColor = AppColor.textPrimary
stackView.axis = .vertical
addSubview(titleLabel)
addSubview(stackView)
titleLabel.snp.makeConstraints { make in
make.top.leading.trailing.equalToSuperview().inset(16)
}
stackView.snp.makeConstraints { make in
make.top.equalTo(titleLabel.snp.bottom).offset(8)
make.leading.trailing.bottom.equalToSuperview().inset(UIEdgeInsets(top: 0, left: 16, bottom: 8, right: 16))
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
/// 使用最新注销影响文案刷新卡片。
func configure(items: [String]) {
stackView.arrangedSubviews.forEach {
stackView.removeArrangedSubview($0)
$0.removeFromSuperview()
}
items.enumerated().forEach { index, text in
stackView.addArrangedSubview(
AccountDeletionConsequenceRowView(
iconName: iconNames.indices.contains(index) ? iconNames[index] : "info.circle",
text: text,
showsDivider: index < items.count - 1
)
)
}
}
}
/// 注销影响说明列表行。
private final class AccountDeletionConsequenceRowView: UIView {
private let iconBackgroundView = UIView()
private let iconView = UIImageView()
private let label = UILabel()
private let divider = UIView()
init(iconName: String, text: String, showsDivider: Bool) {
super.init(frame: .zero)
iconBackgroundView.backgroundColor = AppColor.primaryLight
iconBackgroundView.layer.cornerRadius = 18
iconView.image = UIImage(systemName: iconName)
iconView.tintColor = AppColor.primary
iconView.contentMode = .scaleAspectFit
label.text = text
label.font = .systemFont(ofSize: 14)
label.textColor = AppColor.textPrimary
label.numberOfLines = 0
divider.backgroundColor = AppColor.border
divider.isHidden = !showsDivider
addSubview(iconBackgroundView)
iconBackgroundView.addSubview(iconView)
addSubview(label)
addSubview(divider)
snp.makeConstraints { make in
make.height.greaterThanOrEqualTo(58)
}
iconBackgroundView.snp.makeConstraints { make in
make.leading.centerY.equalToSuperview()
make.width.height.equalTo(36)
}
iconView.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(8)
}
label.snp.makeConstraints { make in
make.leading.equalTo(iconBackgroundView.snp.trailing).offset(12)
make.trailing.equalToSuperview()
make.centerY.equalToSuperview()
}
divider.snp.makeConstraints { make in
make.leading.equalTo(label)
make.trailing.bottom.equalToSuperview()
make.height.equalTo(0.5)
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
/// 注销流程通用说明条。
private final class AccountDeletionNoticeView: UIView {
private let iconView = UIImageView()
private let label = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = AppColor.infoBackground
layer.cornerRadius = 12
addSubview(iconView)
addSubview(label)
iconView.tintColor = AppColor.primary
iconView.contentMode = .scaleAspectFit
label.font = .systemFont(ofSize: 14)
label.textColor = AppColor.textSecondary
label.numberOfLines = 0
iconView.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(16)
make.centerY.equalToSuperview()
make.width.height.equalTo(24)
}
label.snp.makeConstraints { make in
make.leading.equalTo(iconView.snp.trailing).offset(12)
make.trailing.equalToSuperview().offset(-16)
make.top.bottom.equalToSuperview().inset(14)
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
/// 配置说明条图标与文案。
func configure(iconName: String, text: String) {
iconView.image = UIImage(systemName: iconName)
label.text = text
}
}
@@ -14,6 +14,12 @@ final class SettingViewController: BaseViewController {
private let contentView = UIView()
private let cardView = UIView()
private let rowsStack = UIStackView()
private let dangerCardView = UIView()
private let accountDeletionRow = SettingMenuRow(
title: "注销账号",
titleColor: AppColor.danger,
showsDivider: false
)
private let versionRow = SettingMenuRow(title: "系统版本", showsChevron: false)
private let copyrightLabel = UILabel()
@@ -41,12 +47,18 @@ final class SettingViewController: BaseViewController {
view.addSubview(contentView)
contentView.addSubview(cardView)
cardView.addSubview(rowsStack)
contentView.addSubview(dangerCardView)
dangerCardView.addSubview(accountDeletionRow)
contentView.addSubview(copyrightLabel)
cardView.backgroundColor = .white
cardView.layer.cornerRadius = 12
cardView.clipsToBounds = true
dangerCardView.backgroundColor = .white
dangerCardView.layer.cornerRadius = 12
dangerCardView.clipsToBounds = true
rowsStack.axis = .vertical
copyrightLabel.text = "Copyright © 2025 All Rights Reserved\n苏ICP备2025157647号"
@@ -68,6 +80,7 @@ final class SettingViewController: BaseViewController {
rows[2].addTarget(self, action: #selector(copyDownloadTapped), for: .touchUpInside)
rows[3].addTarget(self, action: #selector(userAgreementTapped), for: .touchUpInside)
rows[4].addTarget(self, action: #selector(privacyTapped), for: .touchUpInside)
accountDeletionRow.addTarget(self, action: #selector(accountDeletionTapped), for: .touchUpInside)
}
override func setupConstraints() {
@@ -80,6 +93,13 @@ final class SettingViewController: BaseViewController {
rowsStack.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16))
}
dangerCardView.snp.makeConstraints { make in
make.top.equalTo(cardView.snp.bottom).offset(16)
make.leading.trailing.equalToSuperview()
}
accountDeletionRow.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16))
}
copyrightLabel.snp.makeConstraints { make in
make.leading.trailing.equalToSuperview()
make.bottom.equalToSuperview().offset(-16)
@@ -119,6 +139,10 @@ final class SettingViewController: BaseViewController {
openAgreement(.privacyPolicy)
}
@objc private func accountDeletionTapped() {
navigationController?.pushViewController(AccountDeletionViewController(), animated: true)
}
private func openAgreement(_ kind: SettingAgreementKind) {
let destination = viewModel.agreementDestination(for: kind)
navigationController?.pushViewController(
@@ -141,6 +165,7 @@ final class SettingMenuRow: UIControl {
init(
title: String,
value: String? = nil,
titleColor: UIColor = UIColor(hex: 0x4B5563),
valueColor: UIColor = AppColor.textPrimary,
showsChevron: Bool = true,
showsDivider: Bool = true
@@ -150,6 +175,7 @@ final class SettingMenuRow: UIControl {
setupUI()
setupConstraints()
titleLabel.text = title
titleLabel.textColor = titleColor
valueLabel.text = value
valueLabel.textColor = valueColor
chevronImageView.isHidden = !showsChevron
@@ -175,7 +201,6 @@ final class SettingMenuRow: UIControl {
private func setupUI() {
titleLabel.font = .systemFont(ofSize: 14)
titleLabel.textColor = UIColor(hex: 0x4B5563)
valueLabel.font = .systemFont(ofSize: 14)
valueLabel.textColor = AppColor.textPrimary