// // TravelAlbumDetailView.swift // suixinkan // // Created by Codex on 2026/6/29. // import SwiftUI /// 旅拍相册详情页,展示素材网格与管理操作。 struct TravelAlbumDetailView: View { let albumID: Int @EnvironmentObject private var accountContext: AccountContext @Environment(\.travelAlbumAPI) private var travelAlbumAPI @EnvironmentObject private var toastCenter: ToastCenter @Environment(\.globalLoading) private var globalLoading @Environment(\.dismiss) private var dismiss @StateObject private var viewModel = TravelAlbumDetailViewModel() @State private var showDeleteConfirm = false private var photoStore: WiredTransferPhotoStore { WiredTransferPhotoStore( accountPrefixProvider: { accountContext.accountCachePrefix ?? "guest_" }, userIDProvider: { accountContext.profile?.userId ?? "" } ) } var body: some View { ScrollView { VStack(alignment: .leading, spacing: AppMetrics.Spacing.medium) { if let album = viewModel.album { albumHeader(album) } if viewModel.materials.isEmpty, !viewModel.isLoading { AppContentUnavailableView("暂无素材", systemImage: "photo.on.rectangle") .frame(maxWidth: .infinity, minHeight: 200) } else { LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible())], spacing: 8) { ForEach(viewModel.materials) { material in materialCell(material) } } } } .padding(.horizontal, AppMetrics.Spacing.pageHorizontal) .padding(.vertical, AppMetrics.Spacing.medium) } .background(Color(hex: 0xF5F7FA)) .navigationTitle(viewModel.album?.name ?? "相册详情") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItemGroup(placement: .topBarTrailing) { if let album = viewModel.album { NavigationLink { WiredCameraTransferView(context: WiredTransferContext( albumId: album.id, albumName: album.name, phone: album.displayPhone, orderNumber: album.orderNumber )) } label: { Image(systemName: "cable.connector") } .accessibilityLabel("有线传图") } Button(role: .destructive) { showDeleteConfirm = true } label: { Image(systemName: "trash") } .accessibilityLabel("删除相册") } } .confirmationDialog("确认删除该相册?", isPresented: $showDeleteConfirm, titleVisibility: .visible) { Button("删除", role: .destructive) { Task { await deleteAlbum() } } Button("取消", role: .cancel) {} } .task(id: albumID) { globalLoading.show() defer { globalLoading.hide() } await viewModel.load(api: travelAlbumAPI, albumID: albumID) } .refreshable { await viewModel.load(api: travelAlbumAPI, albumID: albumID) } .onChange(of: viewModel.errorMessage) { message in guard let message, !message.isEmpty else { return } toastCenter.show(message) } } private func albumHeader(_ album: TravelAlbumItem) -> some View { VStack(alignment: .leading, spacing: 8) { Text(album.type == 1 ? "先拍再买" : "买了再拍") .font(.system(size: AppMetrics.FontSize.caption)) .foregroundStyle(AppDesign.primary) if !album.displayPhone.isEmpty { Label(album.displayPhone, systemImage: "phone.fill") .font(.system(size: AppMetrics.FontSize.subheadline)) .foregroundStyle(AppDesign.textSecondary) } if !album.orderNumber.isEmpty { Text("订单号:\(album.orderNumber)") .font(.system(size: AppMetrics.FontSize.caption)) .foregroundStyle(AppDesign.textSecondary) } } .frame(maxWidth: .infinity, alignment: .leading) .padding(AppMetrics.Spacing.large) .background(Color.white) .clipShape(RoundedRectangle(cornerRadius: AppMetrics.CornerRadius.card)) } private func materialCell(_ material: TravelAlbumMaterial) -> some View { ZStack(alignment: .topTrailing) { RemoteImage( urlString: material.coverURL.isEmpty ? material.fileURL : material.coverURL, contentMode: .fill ) { Color(hex: 0xE5E7EB) } .frame(maxWidth: .infinity, maxHeight: .infinity) Menu { Button(role: .destructive) { Task { await viewModel.deleteMaterial(api: travelAlbumAPI, materialID: material.id, albumID: albumID) } } label: { Label("删除", systemImage: "trash") } } label: { Image(systemName: "ellipsis") .padding(6) .background(Color.black.opacity(0.35)) .foregroundStyle(.white) .clipShape(Circle()) .padding(4) } } .aspectRatio(1, contentMode: .fit) .frame(maxWidth: .infinity) .clipShape(RoundedRectangle(cornerRadius: 8)) } private func deleteAlbum() async { globalLoading.show() defer { globalLoading.hide() } let success = await viewModel.deleteAlbum(api: travelAlbumAPI, albumID: albumID, photoStore: photoStore) if success { toastCenter.show("相册已删除") dismiss() } } }