未绑定相册的新拍照片被错误过滤导致列表始终为空;同时调整入口卡片点击区域与详情素材网格布局。 Co-authored-by: Cursor <cursoragent@cursor.com>
222 lines
9.3 KiB
Swift
222 lines
9.3 KiB
Swift
//
|
||
// TravelAlbumEntryView.swift
|
||
// suixinkan
|
||
//
|
||
// Created by Codex on 2026/6/29.
|
||
//
|
||
|
||
import SwiftUI
|
||
|
||
/// 旅拍相册入口页,展示任务列表与新建入口。
|
||
struct TravelAlbumEntryView: View {
|
||
@EnvironmentObject private var accountContext: AccountContext
|
||
@Environment(\.travelAlbumAPI) private var travelAlbumAPI
|
||
@EnvironmentObject private var toastCenter: ToastCenter
|
||
@Environment(\.globalLoading) private var globalLoading
|
||
|
||
@StateObject private var viewModel = TravelAlbumEntryViewModel()
|
||
|
||
var body: some View {
|
||
ScrollView {
|
||
VStack(spacing: AppMetrics.Spacing.medium) {
|
||
createHeroCard
|
||
if viewModel.isLoading, viewModel.albums.isEmpty {
|
||
ProgressView()
|
||
.frame(maxWidth: .infinity, minHeight: 200)
|
||
} else if viewModel.albums.isEmpty {
|
||
AppContentUnavailableView("暂无相册任务", systemImage: "photo.on.rectangle.angled", description: Text("点击“新建相册任务”开始创建任务"))
|
||
.frame(maxWidth: .infinity, minHeight: 240)
|
||
} else {
|
||
LazyVStack(spacing: AppMetrics.Spacing.medium) {
|
||
ForEach(viewModel.albums) { album in
|
||
TravelAlbumCard(
|
||
album: album,
|
||
onOpenDetail: { TravelAlbumSession.currentAlbum = album },
|
||
onOpenWiredTransfer: { TravelAlbumSession.currentAlbum = album },
|
||
onOpenCode: {
|
||
Task {
|
||
await viewModel.openAlbumCode(api: travelAlbumAPI, album: album)
|
||
}
|
||
}
|
||
)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
.padding(.horizontal, AppMetrics.Spacing.pageHorizontal)
|
||
.padding(.vertical, AppMetrics.Spacing.medium)
|
||
}
|
||
.background(Color(hex: 0xF5F7FA))
|
||
.navigationTitle("新增相册")
|
||
.navigationBarTitleDisplayMode(.inline)
|
||
.refreshable {
|
||
await reload(showLoading: false)
|
||
}
|
||
.task {
|
||
await reload(showLoading: viewModel.albums.isEmpty)
|
||
}
|
||
.sheet(isPresented: $viewModel.showCreateSheet) {
|
||
CreateTravelAlbumSheet(
|
||
orders: viewModel.bindableOrders,
|
||
isCreating: viewModel.isCreating,
|
||
onConfirm: { mode, freeCount, singlePrice, packagePrice, order in
|
||
Task {
|
||
let success = await viewModel.confirmCreateTask(
|
||
api: travelAlbumAPI,
|
||
mode: mode,
|
||
freeCount: freeCount,
|
||
singlePrice: singlePrice,
|
||
packagePrice: packagePrice,
|
||
order: order,
|
||
scenicSelected: accountContext.currentScenic != nil
|
||
)
|
||
if success {
|
||
toastCenter.show("任务创建成功")
|
||
} else if let message = viewModel.errorMessage {
|
||
toastCenter.show(message)
|
||
}
|
||
}
|
||
}
|
||
)
|
||
.presentationDetents([.large])
|
||
}
|
||
.sheet(item: $viewModel.codeSheet) { state in
|
||
TravelAlbumCodeSheet(state: state)
|
||
}
|
||
.onChange(of: viewModel.errorMessage) { message in
|
||
guard let message, !message.isEmpty, !viewModel.showCreateSheet else { return }
|
||
toastCenter.show(message)
|
||
}
|
||
}
|
||
|
||
private var createHeroCard: some View {
|
||
Button {
|
||
Task {
|
||
await viewModel.openCreateSheet(
|
||
api: travelAlbumAPI,
|
||
scenicSelected: accountContext.currentScenic != nil
|
||
)
|
||
}
|
||
} label: {
|
||
HStack(spacing: AppMetrics.Spacing.medium) {
|
||
Image(systemName: "plus.circle.fill")
|
||
.font(.system(size: 28))
|
||
.foregroundStyle(AppDesign.primary)
|
||
VStack(alignment: .leading, spacing: 4) {
|
||
Text(viewModel.isCreating ? "新建中..." : "新建相册任务")
|
||
.font(.system(size: AppMetrics.FontSize.title3, weight: .semibold))
|
||
.foregroundStyle(AppDesign.textPrimary)
|
||
Text("创建旅拍相册并开始有线传图")
|
||
.font(.system(size: AppMetrics.FontSize.subheadline))
|
||
.foregroundStyle(AppDesign.textSecondary)
|
||
}
|
||
Spacer()
|
||
Image(systemName: "chevron.right")
|
||
.foregroundStyle(AppDesign.placeholder)
|
||
}
|
||
.padding(AppMetrics.Spacing.large)
|
||
.background(Color.white)
|
||
.clipShape(RoundedRectangle(cornerRadius: AppMetrics.CornerRadius.card))
|
||
}
|
||
.buttonStyle(.plain)
|
||
.disabled(viewModel.isCreating)
|
||
}
|
||
|
||
private func reload(showLoading: Bool) async {
|
||
if showLoading { globalLoading.show() }
|
||
defer { if showLoading { globalLoading.hide() } }
|
||
await viewModel.loadAlbums(api: travelAlbumAPI)
|
||
}
|
||
}
|
||
|
||
private struct TravelAlbumCard: View {
|
||
let album: TravelAlbumItem
|
||
let onOpenDetail: () -> Void
|
||
let onOpenWiredTransfer: () -> Void
|
||
let onOpenCode: () -> Void
|
||
|
||
var body: some View {
|
||
NavigationLink {
|
||
TravelAlbumDetailView(albumID: album.id)
|
||
.onAppear { onOpenDetail() }
|
||
} label: {
|
||
VStack(alignment: .leading, spacing: AppMetrics.Spacing.small) {
|
||
HStack(alignment: .top, spacing: AppMetrics.Spacing.medium) {
|
||
RemoteImage(urlString: album.coverURL) {
|
||
Color(hex: 0xE5E7EB)
|
||
.overlay { Image(systemName: "photo").foregroundStyle(AppDesign.placeholder) }
|
||
}
|
||
.frame(width: 72, height: 72)
|
||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
||
|
||
VStack(alignment: .leading, spacing: 6) {
|
||
Text(album.name)
|
||
.font(.system(size: AppMetrics.FontSize.title3, weight: .semibold))
|
||
.foregroundStyle(AppDesign.textPrimary)
|
||
.lineLimit(1)
|
||
if !album.displayPhone.isEmpty {
|
||
Label(album.displayPhone, systemImage: "phone.fill")
|
||
.font(.system(size: AppMetrics.FontSize.caption))
|
||
.foregroundStyle(AppDesign.textSecondary)
|
||
}
|
||
Text(album.type == 1 ? "先拍再买" : "买了再拍")
|
||
.font(.system(size: AppMetrics.FontSize.caption))
|
||
.foregroundStyle(AppDesign.primary)
|
||
}
|
||
Spacer()
|
||
Image(systemName: "chevron.right")
|
||
.font(.system(size: AppMetrics.FontSize.caption, weight: .semibold))
|
||
.foregroundStyle(AppDesign.placeholder)
|
||
}
|
||
|
||
// 占位底部操作区高度,保证整卡可点进详情且布局与真实按钮对齐
|
||
HStack(spacing: AppMetrics.Spacing.small) {
|
||
actionChip(title: "拍传", systemImage: "cable.connector")
|
||
actionChip(title: "相册码", systemImage: "qrcode")
|
||
}
|
||
.hidden()
|
||
}
|
||
.padding(AppMetrics.Spacing.large)
|
||
.frame(maxWidth: .infinity, alignment: .leading)
|
||
.background(Color.white)
|
||
.clipShape(RoundedRectangle(cornerRadius: AppMetrics.CornerRadius.card))
|
||
.contentShape(Rectangle())
|
||
}
|
||
.buttonStyle(.plain)
|
||
.overlay(alignment: .bottomLeading) {
|
||
HStack(spacing: AppMetrics.Spacing.small) {
|
||
NavigationLink {
|
||
WiredCameraTransferView(context: WiredTransferContext(
|
||
albumId: album.id,
|
||
albumName: album.name,
|
||
phone: album.displayPhone,
|
||
orderNumber: album.orderNumber
|
||
))
|
||
.onAppear { onOpenWiredTransfer() }
|
||
} label: {
|
||
actionChip(title: "拍传", systemImage: "cable.connector")
|
||
}
|
||
|
||
Button(action: onOpenCode) {
|
||
actionChip(title: "相册码", systemImage: "qrcode")
|
||
}
|
||
}
|
||
.padding(.horizontal, AppMetrics.Spacing.large)
|
||
.padding(.bottom, AppMetrics.Spacing.large)
|
||
}
|
||
}
|
||
|
||
private func actionChip(title: String, systemImage: String) -> some View {
|
||
HStack(spacing: 4) {
|
||
Image(systemName: systemImage)
|
||
Text(title)
|
||
}
|
||
.font(.system(size: AppMetrics.FontSize.caption, weight: .medium))
|
||
.foregroundStyle(AppDesign.primary)
|
||
.padding(.horizontal, 10)
|
||
.padding(.vertical, 6)
|
||
.background(AppDesign.primary.opacity(0.08))
|
||
.clipShape(Capsule())
|
||
}
|
||
}
|