完善样片管理列表、详情与上传页 UI 交互。
对齐 Android 样片模块布局与加载状态,补充共享 UI 组件与图片资源,并增强列表解析容错与单元测试覆盖。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -8,7 +8,7 @@ import SnapKit
|
||||
import UIKit
|
||||
|
||||
/// 样片相册列表页,对齐 Android `SampleListScreen`。
|
||||
final class SampleListViewController: BaseViewController {
|
||||
final class SampleListViewController: SampleBaseViewController {
|
||||
private let viewModel: SampleListViewModel
|
||||
private let api: any SampleManagementServing
|
||||
|
||||
@ -45,11 +45,13 @@ final class SampleListViewController: BaseViewController {
|
||||
}
|
||||
|
||||
override func setupNavigationBar() {
|
||||
title = "样片相册列表"
|
||||
super.setupNavigationBar()
|
||||
setSampleTitle("样片相册列表")
|
||||
}
|
||||
|
||||
override func setupUI() {
|
||||
view.backgroundColor = AppColor.pageBackground
|
||||
super.setupUI()
|
||||
view.backgroundColor = SampleManagementUITokens.pageBackground
|
||||
|
||||
searchContainer.backgroundColor = .white
|
||||
searchBox.backgroundColor = UIColor(hex: 0xF4F4F4)
|
||||
@ -65,7 +67,7 @@ final class SampleListViewController: BaseViewController {
|
||||
filterContainer.backgroundColor = .white
|
||||
filterButton.backgroundColor = UIColor(hex: 0xF4F4F4)
|
||||
filterButton.layer.cornerRadius = 4
|
||||
filterTitleLabel.text = "筛选"
|
||||
filterTitleLabel.text = SampleFilterStatus.all.title
|
||||
filterTitleLabel.font = .systemFont(ofSize: 14)
|
||||
filterTitleLabel.textColor = UIColor(hex: 0x4B5563)
|
||||
filterChevronView.tintColor = UIColor(hex: 0x4B5563)
|
||||
@ -80,7 +82,7 @@ final class SampleListViewController: BaseViewController {
|
||||
filterDropdownStack.axis = .vertical
|
||||
filterDropdownStack.spacing = 0
|
||||
|
||||
tableView.backgroundColor = AppColor.pageBackground
|
||||
tableView.backgroundColor = SampleManagementUITokens.pageBackground
|
||||
tableView.separatorStyle = .none
|
||||
tableView.estimatedRowHeight = 152
|
||||
tableView.rowHeight = UITableView.automaticDimension
|
||||
@ -95,10 +97,15 @@ final class SampleListViewController: BaseViewController {
|
||||
withIdentifier: SampleListCell.reuseIdentifier,
|
||||
for: indexPath
|
||||
) as! SampleListCell
|
||||
cell.apply(item: item)
|
||||
cell.onToggle = { [weak self] checked in
|
||||
cell.apply(item: item, isOperating: self?.viewModel.operatingSampleIDs.contains(item.id) == true)
|
||||
cell.onToggle = { [weak self, weak cell] checked in
|
||||
guard let self else { return }
|
||||
Task { await self.viewModel.toggleListing(sampleId: item.id, checked: checked, api: self.api) }
|
||||
Task {
|
||||
let resolved = await self.viewModel.toggleListing(sampleId: item.id, checked: checked, api: self.api)
|
||||
await MainActor.run {
|
||||
cell?.resolveToggle(sampleId: item.id, isOn: resolved)
|
||||
}
|
||||
}
|
||||
}
|
||||
return cell
|
||||
}
|
||||
@ -135,7 +142,7 @@ final class SampleListViewController: BaseViewController {
|
||||
|
||||
override func setupConstraints() {
|
||||
searchContainer.snp.makeConstraints { make in
|
||||
make.top.equalTo(view.safeAreaLayoutGuide)
|
||||
make.top.equalToSuperview()
|
||||
make.leading.trailing.equalToSuperview()
|
||||
make.height.equalTo(68)
|
||||
}
|
||||
@ -232,7 +239,7 @@ final class SampleListViewController: BaseViewController {
|
||||
|
||||
@MainActor
|
||||
private func applyViewModel() {
|
||||
filterTitleLabel.text = viewModel.filterStatus == .all ? "筛选" : viewModel.filterStatus.title
|
||||
filterTitleLabel.text = viewModel.filterStatus.title
|
||||
filterOptionButtons.forEach { status, button in
|
||||
let selected = status == viewModel.filterStatus
|
||||
button.setTitleColor(selected ? AppColor.primary : AppColor.textPrimary, for: .normal)
|
||||
@ -342,11 +349,13 @@ private final class SampleListCell: UITableViewCell {
|
||||
private let scenicSpotLabel = UILabel()
|
||||
private let statusBadge = SampleStatusBadgeView()
|
||||
private let listingSwitch = UISwitch()
|
||||
private let listingActivityIndicator = UIActivityIndicatorView(style: .medium)
|
||||
private let statsStack = UIStackView()
|
||||
private let shareStatView = SampleStatView(iconName: "sample_ic_share_count")
|
||||
private let likeStatView = SampleStatView(iconName: "sample_ic_like_count")
|
||||
private let collectStatView = SampleStatView(iconName: "sample_ic_collect_count")
|
||||
private var isApplying = false
|
||||
private var representedSampleID: Int?
|
||||
|
||||
var onToggle: ((Bool) -> Void)?
|
||||
|
||||
@ -360,13 +369,23 @@ private final class SampleListCell: UITableViewCell {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func apply(item: SampleMaterialItem) {
|
||||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
representedSampleID = nil
|
||||
onToggle = nil
|
||||
}
|
||||
|
||||
func apply(item: SampleMaterialItem, isOperating: Bool) {
|
||||
isApplying = true
|
||||
representedSampleID = item.id
|
||||
titleLabel.text = item.name
|
||||
projectLabel.text = "项目: \(item.projectName)"
|
||||
scenicSpotLabel.text = "打卡地: \(item.scenicSpotName)"
|
||||
statusBadge.apply(status: item.status)
|
||||
listingSwitch.setOn(item.listingStatus == 1, animated: false)
|
||||
listingSwitch.isEnabled = item.status == 1 && !isOperating
|
||||
listingSwitch.isHidden = isOperating
|
||||
isOperating ? listingActivityIndicator.startAnimating() : listingActivityIndicator.stopAnimating()
|
||||
shareStatView.setCount(item.shareCount)
|
||||
likeStatView.setCount(item.likesCount)
|
||||
collectStatView.setCount(item.collectCount)
|
||||
@ -381,10 +400,21 @@ private final class SampleListCell: UITableViewCell {
|
||||
isApplying = false
|
||||
}
|
||||
|
||||
/// 请求结束后恢复服务端确认的开关状态,避免审核拒绝或接口失败时停留在错误位置。
|
||||
func resolveToggle(sampleId: Int, isOn: Bool) {
|
||||
guard representedSampleID == sampleId else { return }
|
||||
isApplying = true
|
||||
listingSwitch.setOn(isOn, animated: true)
|
||||
listingSwitch.isEnabled = true
|
||||
listingSwitch.isHidden = false
|
||||
listingActivityIndicator.stopAnimating()
|
||||
isApplying = false
|
||||
}
|
||||
|
||||
private func setupUI() {
|
||||
selectionStyle = .none
|
||||
backgroundColor = AppColor.pageBackground
|
||||
contentView.backgroundColor = AppColor.pageBackground
|
||||
backgroundColor = SampleManagementUITokens.pageBackground
|
||||
contentView.backgroundColor = SampleManagementUITokens.pageBackground
|
||||
|
||||
cardView.backgroundColor = .white
|
||||
cardView.layer.cornerRadius = 12
|
||||
@ -411,7 +441,10 @@ private final class SampleListCell: UITableViewCell {
|
||||
}
|
||||
|
||||
listingSwitch.onTintColor = AppColor.primary
|
||||
listingSwitch.accessibilityLabel = "样片上下架"
|
||||
listingSwitch.addTarget(self, action: #selector(switchChanged), for: .valueChanged)
|
||||
listingActivityIndicator.color = AppColor.primary
|
||||
listingActivityIndicator.hidesWhenStopped = true
|
||||
|
||||
statsStack.axis = .horizontal
|
||||
statsStack.spacing = 16
|
||||
@ -425,11 +458,13 @@ private final class SampleListCell: UITableViewCell {
|
||||
cardView.addSubview(scenicSpotLabel)
|
||||
cardView.addSubview(statusBadge)
|
||||
cardView.addSubview(listingSwitch)
|
||||
cardView.addSubview(listingActivityIndicator)
|
||||
cardView.addSubview(statsStack)
|
||||
[shareStatView, likeStatView, collectStatView].forEach(statsStack.addArrangedSubview)
|
||||
|
||||
cardView.snp.makeConstraints { make in
|
||||
make.top.bottom.equalToSuperview().inset(8)
|
||||
make.top.equalToSuperview()
|
||||
make.bottom.equalToSuperview().inset(16)
|
||||
make.leading.trailing.equalToSuperview().inset(16)
|
||||
}
|
||||
coverImageView.snp.makeConstraints { make in
|
||||
@ -463,6 +498,9 @@ private final class SampleListCell: UITableViewCell {
|
||||
make.trailing.equalToSuperview().inset(12)
|
||||
make.leading.greaterThanOrEqualTo(statusBadge.snp.trailing).offset(8)
|
||||
}
|
||||
listingActivityIndicator.snp.makeConstraints { make in
|
||||
make.center.equalTo(listingSwitch)
|
||||
}
|
||||
statsStack.snp.makeConstraints { make in
|
||||
make.top.equalTo(statusBadge.snp.bottom).offset(10)
|
||||
make.leading.equalTo(titleLabel)
|
||||
@ -473,6 +511,9 @@ private final class SampleListCell: UITableViewCell {
|
||||
|
||||
@objc private func switchChanged() {
|
||||
guard !isApplying else { return }
|
||||
listingSwitch.isEnabled = false
|
||||
listingSwitch.isHidden = true
|
||||
listingActivityIndicator.startAnimating()
|
||||
onToggle?(listingSwitch.isOn)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user