// // 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 { 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() } 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") } NavigationLink { TravelAlbumDetailView(albumID: album.id) .onAppear { onOpenDetail() } } label: { actionChip(title: "详情", systemImage: "info.circle") } } } .padding(AppMetrics.Spacing.large) .background(Color.white) .clipShape(RoundedRectangle(cornerRadius: AppMetrics.CornerRadius.card)) } 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()) } }