为旅拍 OTG 上传增加 client_photo_id,并按服务端已登记 ID 同步本地上传状态。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-16 19:13:19 +08:00
parent 3d5ad8a614
commit b019e1e494
11 changed files with 323 additions and 115 deletions

View File

@ -12,19 +12,16 @@ final class CreateTravelAlbumSheetViewController: BaseViewController {
private let viewModel: TravelAlbumEntryViewModel
private let api: any TravelAlbumServing
private var mode: TravelAlbumEntryViewModel.CreateMode = .preShoot
private var selectedOrder: TravelAlbumAvailableOrder?
private let mode: TravelAlbumEntryViewModel.CreateMode = .preShoot
private let scrollView = UIScrollView()
private let contentView = UIView()
private let titleLabel = UILabel()
private let preShootCard = TravelAlbumModeOptionView()
private let preOrderCard = TravelAlbumModeOptionView()
private let fieldsStack = UIStackView()
private let freeCountField = UITextField()
private let singlePriceField = UITextField()
private let packagePriceField = UITextField()
private let orderButton = UIButton(type: .system)
private let cancelButton = UIButton(type: .system)
private let confirmButton = UIButton(type: .system)
@ -58,11 +55,6 @@ final class CreateTravelAlbumSheetViewController: BaseViewController {
desc: "拍完后分享给用户,用户在小程序上选择性购买",
selected: true
)
preOrderCard.apply(
title: "买了再拍",
desc: "表示用户已经在小程序上下过单了,绑定对应订单上传后用户可以直接选片",
selected: false
)
fieldsStack.axis = .vertical
fieldsStack.spacing = 14
@ -71,23 +63,12 @@ final class CreateTravelAlbumSheetViewController: BaseViewController {
configureTextField(singlePriceField, placeholder: "请输入单张照片价格", keyboardType: .decimalPad)
configureTextField(packagePriceField, placeholder: "请输入打包价格", keyboardType: .decimalPad)
orderButton.contentHorizontalAlignment = .left
orderButton.setTitle("请选择订单", for: .normal)
orderButton.setTitleColor(AppColor.textTertiary, for: .normal)
orderButton.titleLabel?.font = .systemFont(ofSize: 14)
orderButton.layer.cornerRadius = 8
orderButton.layer.borderColor = AppColor.border.cgColor
orderButton.layer.borderWidth = 1
orderButton.setConfigurationContentInsets(
NSDirectionalEdgeInsets(top: 12, leading: 12, bottom: 12, trailing: 12)
)
configureActionButton(cancelButton, title: "取消", backgroundColor: UIColor(hex: 0xF4F4F4), titleColor: AppColor.textSecondary)
configureActionButton(confirmButton, title: "确定", backgroundColor: AppColor.primary, titleColor: .white)
view.addSubview(scrollView)
scrollView.addSubview(contentView)
[titleLabel, preShootCard, preOrderCard, fieldsStack, cancelButton, confirmButton].forEach(contentView.addSubview)
[titleLabel, preShootCard, fieldsStack, cancelButton, confirmButton].forEach(contentView.addSubview)
rebuildFields()
}
@ -107,12 +88,8 @@ final class CreateTravelAlbumSheetViewController: BaseViewController {
make.top.equalTo(titleLabel.snp.bottom).offset(18)
make.leading.trailing.equalToSuperview().inset(16)
}
preOrderCard.snp.makeConstraints { make in
make.top.equalTo(preShootCard.snp.bottom).offset(10)
make.leading.trailing.equalTo(preShootCard)
}
fieldsStack.snp.makeConstraints { make in
make.top.equalTo(preOrderCard.snp.bottom).offset(18)
make.top.equalTo(preShootCard.snp.bottom).offset(18)
make.leading.trailing.equalTo(preShootCard)
}
cancelButton.snp.makeConstraints { make in
@ -130,9 +107,6 @@ final class CreateTravelAlbumSheetViewController: BaseViewController {
}
override func bindActions() {
preShootCard.addTarget(self, action: #selector(preShootTapped), for: .touchUpInside)
preOrderCard.addTarget(self, action: #selector(preOrderTapped), for: .touchUpInside)
orderButton.addTarget(self, action: #selector(orderTapped), for: .touchUpInside)
cancelButton.addTarget(self, action: #selector(cancelTapped), for: .touchUpInside)
confirmButton.addTarget(self, action: #selector(confirmTapped), for: .touchUpInside)
[freeCountField, singlePriceField, packagePriceField].forEach {
@ -171,13 +145,9 @@ final class CreateTravelAlbumSheetViewController: BaseViewController {
private func rebuildFields() {
fieldsStack.arrangedSubviews.forEach { $0.removeFromSuperview() }
if mode == .preShoot {
fieldsStack.addArrangedSubview(makeFieldGroup(title: "免费张数", required: false, field: freeCountField))
fieldsStack.addArrangedSubview(makeFieldGroup(title: "单张照片价格(元)", required: true, field: singlePriceField))
fieldsStack.addArrangedSubview(makeFieldGroup(title: "打包价格(元)", required: false, field: packagePriceField))
} else {
fieldsStack.addArrangedSubview(makeOrderGroup())
}
fieldsStack.addArrangedSubview(makeFieldGroup(title: "免费张数", required: false, field: freeCountField))
fieldsStack.addArrangedSubview(makeFieldGroup(title: "单张照片价格(元)", required: true, field: singlePriceField))
fieldsStack.addArrangedSubview(makeFieldGroup(title: "打包价格(元)", required: false, field: packagePriceField))
}
private func makeFieldGroup(title: String, required: Bool, field: UITextField) -> UIView {
@ -196,23 +166,6 @@ final class CreateTravelAlbumSheetViewController: BaseViewController {
return container
}
private func makeOrderGroup() -> UIView {
let container = UIView()
let label = UILabel()
label.attributedText = fieldTitle("绑定订单", required: false)
container.addSubview(label)
container.addSubview(orderButton)
label.snp.makeConstraints { make in
make.top.leading.trailing.equalToSuperview()
}
orderButton.snp.makeConstraints { make in
make.top.equalTo(label.snp.bottom).offset(8)
make.leading.trailing.bottom.equalToSuperview()
make.height.greaterThanOrEqualTo(46)
}
return container
}
private func fieldTitle(_ text: String, required: Bool) -> NSAttributedString {
let result = NSMutableAttributedString(
string: text,
@ -224,58 +177,6 @@ final class CreateTravelAlbumSheetViewController: BaseViewController {
return result
}
private func updateMode(_ newMode: TravelAlbumEntryViewModel.CreateMode) {
mode = newMode
preShootCard.apply(
title: "先拍再买",
desc: "拍完后分享给用户,用户在小程序上选择性购买",
selected: mode == .preShoot
)
preOrderCard.apply(
title: "买了再拍",
desc: "表示用户已经在小程序上下过单了,绑定对应订单上传后用户可以直接选片",
selected: mode == .preOrder
)
rebuildFields()
}
private func updateOrderButton() {
guard let order = selectedOrder else {
orderButton.setTitle("请选择订单", for: .normal)
orderButton.setTitleColor(AppColor.textTertiary, for: .normal)
return
}
let title = "\(order.projectName.isEmpty ? "未命名项目" : order.projectName)\n手机号:\(order.userPhone)\n订单号:\(order.orderNumber)"
orderButton.setTitle(title, for: .normal)
orderButton.setTitleColor(AppColor.textPrimary, for: .normal)
orderButton.titleLabel?.numberOfLines = 3
}
@objc private func preShootTapped() {
updateMode(.preShoot)
}
@objc private func preOrderTapped() {
updateMode(.preOrder)
}
@objc private func orderTapped() {
let alert = UIAlertController(title: "绑定订单", message: nil, preferredStyle: .actionSheet)
if viewModel.availableOrders.isEmpty {
alert.addAction(UIAlertAction(title: "暂无可绑定订单", style: .default))
} else {
viewModel.availableOrders.forEach { order in
let title = "\(order.projectName.isEmpty ? "未命名项目" : order.projectName) \(order.userPhone)"
alert.addAction(UIAlertAction(title: title, style: .default) { [weak self] _ in
self?.selectedOrder = order
self?.updateOrderButton()
})
}
}
alert.addAction(UIAlertAction(title: "取消", style: .cancel))
present(alert, animated: true)
}
@objc private func cancelTapped() {
dismiss(animated: true)
}
@ -287,7 +188,7 @@ final class CreateTravelAlbumSheetViewController: BaseViewController {
freeCount: freeCountField.text ?? "",
singlePrice: singlePriceField.text ?? "",
packagePrice: packagePriceField.text ?? "",
order: selectedOrder,
order: nil,
api: api
)
await MainActor.run {

View File

@ -421,13 +421,19 @@ final class WildReportRiskMapViewController: BaseViewController {
let avatarWrap = UIView()
avatarWrap.backgroundColor = AppColor.success.withAlphaComponent(0.12)
avatarWrap.layer.cornerRadius = 28
let avatar = UIImageView(image: UIImage(systemName: clue.avatar ?? "person.crop.circle.fill"))
let placeholder = UIImage(systemName: "person.crop.circle.fill")
let avatar = UIImageView(image: placeholder)
avatar.tintColor = AppColor.success
avatar.contentMode = .scaleAspectFit
avatar.contentMode = .scaleAspectFill
avatar.clipsToBounds = true
avatar.layer.cornerRadius = 28
let avatarURLString = clue.avatar?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
if let avatarURL = URL(string: avatarURLString), !avatarURLString.isEmpty {
avatar.kf.setImage(with: avatarURL, placeholder: placeholder)
}
avatarWrap.addSubview(avatar)
avatar.snp.makeConstraints { make in
make.center.equalToSuperview()
make.size.equalTo(42)
make.edges.equalToSuperview()
}
avatarWrap.snp.makeConstraints { make in
make.size.equalTo(56)