完善实名认证、钱包与打卡点模块 UI 与业务逻辑。

对齐 Android 实名认证流程与审核页、钱包提现/积分兑换界面,优化打卡点列表与详情交互,并补充相关资源、主题 token 与单元测试。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-13 16:28:10 +08:00
parent 07b2b9d459
commit 5138c1c11a
63 changed files with 3101 additions and 745 deletions

View File

@ -17,9 +17,9 @@ final class PunchPointFormViewController: BaseViewController {
private let mapView = PunchPointMapView()
private let zoomStack = UIStackView()
private let zoomInButton = PunchPointMapControlButton(symbol: "plus")
private let zoomOutButton = PunchPointMapControlButton(symbol: "minus")
private let locateButton = PunchPointMapControlButton(symbol: "location.fill")
private let zoomInButton = PunchPointMapControlButton(assetName: "punch_point_zoom_in")
private let zoomOutButton = PunchPointMapControlButton(assetName: "punch_point_zoom_out")
private let locateButton = PunchPointMapControlButton(assetName: "punch_point_map_location")
private let cardView = UIView()
private let scrollView = UIScrollView()
private let formStack = UIStackView()
@ -105,11 +105,11 @@ final class PunchPointFormViewController: BaseViewController {
}
zoomStack.snp.makeConstraints { make in
make.trailing.equalToSuperview().inset(16)
make.bottom.equalTo(cardView.snp.top).offset(-16)
make.bottom.equalTo(locateButton.snp.top).offset(-12)
}
locateButton.snp.makeConstraints { make in
make.trailing.equalToSuperview().inset(16)
make.bottom.equalTo(zoomStack.snp.top).offset(-12)
make.bottom.equalTo(cardView.snp.top).offset(-16)
make.size.equalTo(40)
}
zoomInButton.snp.makeConstraints { make in
@ -148,12 +148,11 @@ final class PunchPointFormViewController: BaseViewController {
Task { @MainActor in self?.applyViewModel() }
}
viewModel.onShowMessage = { [weak self] message in
Task { @MainActor in self?.showToast(message) }
Task { @MainActor in self?.handleMessage(message) }
}
viewModel.onCoordinateChange = { [weak self] coordinate in
Task { @MainActor in
self?.mapView.updateMarker(coordinate: coordinate)
self?.mapView.setCenter(coordinate, zoomLevel: 17)
self?.applyMapCoordinate(coordinate)
}
}
viewModel.onSubmitSuccess = { [weak self] in
@ -232,6 +231,39 @@ final class PunchPointFormViewController: BaseViewController {
return PunchPointImageState(data: data, fileName: fileName)
}
@MainActor
private func handleMessage(_ message: String) {
guard message.contains("定位权限未授予") else {
showToast(message)
return
}
let alert = UIAlertController(title: "无法定位", message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "取消", style: .cancel))
alert.addAction(UIAlertAction(title: "去设置", style: .default) { _ in
guard let url = URL(string: UIApplication.openSettingsURLString) else { return }
UIApplication.shared.open(url)
})
present(alert, animated: true)
}
@MainActor
private func applyMapCoordinate(_ coordinate: CLLocationCoordinate2D) {
if let initialDetail, let original = initialDetail.coordinate {
mapView.updatePunchPointMarker(
coordinate: original,
imageURL: initialDetail.guideImages.first
)
let isOriginal = abs(original.latitude - coordinate.latitude) < 0.000_001
&& abs(original.longitude - coordinate.longitude) < 0.000_001
mapView.updateSelectedMarker(coordinate: isOriginal ? nil : coordinate)
mapView.fitAllMarkers()
} else {
mapView.updatePunchPointMarker(coordinate: nil)
mapView.updateSelectedMarker(coordinate: coordinate)
mapView.setCenter(coordinate, zoomLevel: 17)
}
}
private func updateUploadDialog(_ state: PunchPointUploadDialogState?) {
guard let state else {
progressAlert?.dismiss(animated: true)
@ -329,13 +361,13 @@ private final class PunchPointTextFieldView: UIView {
super.init(frame: .zero)
titleLabel.attributedText = Self.requiredTitle(title)
textField.placeholder = placeholder
textField.font = .systemFont(ofSize: 14)
textField.font = .systemFont(ofSize: 15)
textField.textColor = AppColor.textPrimary
textField.backgroundColor = .white
textField.layer.cornerRadius = 8
textField.layer.borderColor = AppColor.border.cgColor
textField.layer.borderWidth = 1
textField.leftView = UIView(frame: CGRect(x: 0, y: 0, width: 12, height: 44))
textField.leftView = UIView(frame: CGRect(x: 0, y: 0, width: 12, height: 46))
textField.leftViewMode = .always
addSubview(titleLabel)
addSubview(textField)
@ -345,7 +377,7 @@ private final class PunchPointTextFieldView: UIView {
textField.snp.makeConstraints { make in
make.top.equalTo(titleLabel.snp.bottom).offset(8)
make.leading.trailing.bottom.equalToSuperview()
make.height.equalTo(44)
make.height.equalTo(46)
}
textField.addTarget(self, action: #selector(textChanged), for: .editingChanged)
}
@ -395,10 +427,11 @@ private final class PunchPointCoordinateFieldView: UIView {
container.layer.borderWidth = 1
container.layer.borderColor = AppColor.border.cgColor
valueLabel.text = placeholder
valueLabel.font = .systemFont(ofSize: 14)
valueLabel.font = .systemFont(ofSize: 15)
valueLabel.textColor = UIColor(hex: 0x999999)
locateButton.setImage(UIImage(systemName: "location.fill"), for: .normal)
locateButton.tintColor = AppColor.textPrimary
locateButton.setImage(UIImage(named: "punch_point_location"), for: .normal)
locateButton.imageView?.contentMode = .scaleAspectFit
locateButton.accessibilityLabel = "定位打卡点坐标"
addSubview(titleLabel)
addSubview(container)
container.addSubview(valueLabel)
@ -409,7 +442,7 @@ private final class PunchPointCoordinateFieldView: UIView {
container.snp.makeConstraints { make in
make.top.equalTo(titleLabel.snp.bottom).offset(8)
make.leading.trailing.bottom.equalToSuperview()
make.height.equalTo(44)
make.height.equalTo(46)
}
valueLabel.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(12)
@ -457,7 +490,7 @@ private final class PunchPointTextViewFieldView: UIView, UITextViewDelegate {
titleLabel.text = title
titleLabel.font = .systemFont(ofSize: 14, weight: .medium)
titleLabel.textColor = UIColor(hex: 0x333333)
textView.font = .systemFont(ofSize: 14)
textView.font = .systemFont(ofSize: 15)
textView.textColor = AppColor.textPrimary
textView.layer.cornerRadius = 8
textView.layer.borderWidth = 1
@ -476,7 +509,7 @@ private final class PunchPointTextViewFieldView: UIView, UITextViewDelegate {
textView.snp.makeConstraints { make in
make.top.equalTo(titleLabel.snp.bottom).offset(8)
make.leading.trailing.bottom.equalToSuperview()
make.height.equalTo(92)
make.height.equalTo(100)
}
placeholderLabel.snp.makeConstraints { make in
make.top.equalToSuperview().offset(10)
@ -575,7 +608,8 @@ private final class PunchPointImageGridView: UIView {
///
private final class PunchPointImageThumbView: UIControl {
private let imageView = UIImageView()
private let addIcon = UIImageView(image: UIImage(systemName: "plus"))
private let addIcon = UIImageView(image: UIImage(named: "punch_point_image_add"))
private let addLabel = UILabel()
private let deleteButton = UIButton(type: .system)
private let overlayLabel = UILabel()
var onAdd: (() -> Void)?
@ -586,26 +620,35 @@ private final class PunchPointImageThumbView: UIControl {
super.init(frame: frame)
layer.cornerRadius = 8
clipsToBounds = true
backgroundColor = .white
backgroundColor = UIColor(hex: 0xF4F4F4)
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
addIcon.tintColor = AppColor.primary
deleteButton.setImage(UIImage(systemName: "xmark.circle.fill"), for: .normal)
deleteButton.tintColor = .white
addLabel.text = "点击上传"
addLabel.font = .systemFont(ofSize: 12)
addLabel.textColor = UIColor(hex: 0xB6BECA)
addLabel.textAlignment = .center
deleteButton.setImage(UIImage(named: "punch_point_image_delete"), for: .normal)
deleteButton.accessibilityLabel = "删除图片"
overlayLabel.font = .systemFont(ofSize: 14, weight: .medium)
overlayLabel.textColor = .white
overlayLabel.textAlignment = .center
overlayLabel.backgroundColor = UIColor.black.withAlphaComponent(0.45)
addSubview(imageView)
addSubview(addIcon)
addSubview(addLabel)
addSubview(deleteButton)
addSubview(overlayLabel)
imageView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
addIcon.snp.makeConstraints { make in
make.center.equalToSuperview()
make.size.equalTo(28)
make.centerX.equalToSuperview()
make.centerY.equalToSuperview().offset(-8)
make.size.equalTo(24)
}
addLabel.snp.makeConstraints { make in
make.top.equalTo(addIcon.snp.bottom).offset(2)
make.centerX.equalToSuperview()
}
deleteButton.snp.makeConstraints { make in
make.top.trailing.equalToSuperview().inset(4)
@ -624,18 +667,22 @@ private final class PunchPointImageThumbView: UIControl {
}
func applyAdd() {
accessibilityLabel = "点击上传图片"
layer.borderColor = UIColor(hex: 0xE5E7EB).cgColor
layer.borderWidth = 1
imageView.image = nil
imageView.kf.cancelDownloadTask()
addIcon.isHidden = false
addLabel.isHidden = false
deleteButton.isHidden = true
overlayLabel.isHidden = true
}
func apply(item: PunchPointImageState) {
accessibilityLabel = item.errorMessage == nil ? "打卡点图片" : "图片上传失败,点击重试"
layer.borderWidth = 0
addIcon.isHidden = true
addLabel.isHidden = true
deleteButton.isHidden = item.isUploading
if let urlText = item.previewURL, let url = URL(string: urlText), !urlText.isEmpty {
imageView.kf.setImage(with: url)