// // TaskDetailView.swift // suixinkan // // Created by Codex on 2026/6/23. // import SwiftUI /// 任务详情页面,展示任务基础信息、关联订单和任务结果。 struct TaskDetailView: View { @Environment(\.taskAPI) private var taskAPI @EnvironmentObject private var toastCenter: ToastCenter @Environment(\.globalLoading) private var globalLoading let taskId: Int let summary: PhotographerTaskItem? @StateObject private var viewModel = TaskDetailViewModel() var body: some View { ScrollView { VStack(spacing: AppMetrics.Spacing.medium) { headerSection orderSection resultSection } .padding(AppMetrics.Spacing.pageHorizontal) } .background(Color(hex: 0xF5F7FA)) .navigationTitle("任务详情") .navigationBarTitleDisplayMode(.inline) .task { await globalLoading.withOptionalLoading(viewModel.detail == nil) { await viewModel.load(api: taskAPI, taskId: taskId) } if let message = viewModel.errorMessage { toastCenter.show(message) } } } /// 任务头部信息区域。 private var headerSection: some View { VStack(alignment: .leading, spacing: AppMetrics.Spacing.small) { HStack(alignment: .top) { Text(displayName) .font(.system(size: AppMetrics.FontSize.title3, weight: .semibold)) .foregroundStyle(AppDesign.textPrimary) .lineLimit(2) Spacer() Text(displayStatus) .font(.system(size: AppMetrics.FontSize.caption, weight: .semibold)) .foregroundStyle(AppDesign.primary) .padding(.horizontal, AppMetrics.Spacing.small) .padding(.vertical, AppMetrics.Spacing.xxSmall) .background(AppDesign.primarySoft, in: Capsule()) } TaskInfoRow(title: "任务备注", value: detail?.photogRemark ?? summary?.photogRemark ?? "暂无") TaskInfoRow(title: "创建时间", value: detail?.createdAt ?? summary?.createdAt ?? "暂无") TaskInfoRow(title: "紧急小时", value: detail.map { "\($0.urgentHour) 小时" } ?? "暂无") TaskInfoRow(title: "编辑人员", value: detail?.editorName.nonEmpty ?? summary?.editorName.nonEmpty ?? "暂无") TaskInfoRow(title: "编辑电话", value: detail?.editorPhone.nonEmpty ?? "暂无") } .padding(AppMetrics.Spacing.medium) .background(.white, in: RoundedRectangle(cornerRadius: AppMetrics.CornerRadius.card)) } /// 关联订单区域。 private var orderSection: some View { VStack(alignment: .leading, spacing: AppMetrics.Spacing.small) { Text("关联订单") .font(.system(size: AppMetrics.FontSize.body, weight: .semibold)) .foregroundStyle(AppDesign.textPrimary) TaskInfoRow(title: "项目名称", value: detail?.order?.projectName.nonEmpty ?? summary?.orderUserNickname.nonEmpty ?? "暂无") TaskInfoRow(title: "订单号", value: detail?.order?.orderNumber.nonEmpty ?? summary?.orderNumber.nonEmpty ?? "暂无") TaskInfoRow(title: "手机号", value: detail?.order?.userPhone.nonEmpty ?? summary?.orderUserPhone.nonEmpty ?? "暂无") TaskInfoRow(title: "支付时间", value: detail?.order?.payTime.nonEmpty ?? "暂无") } .padding(AppMetrics.Spacing.medium) .background(.white, in: RoundedRectangle(cornerRadius: AppMetrics.CornerRadius.card)) } /// 任务结果区域。 private var resultSection: some View { VStack(alignment: .leading, spacing: AppMetrics.Spacing.small) { Text("任务结果") .font(.system(size: AppMetrics.FontSize.body, weight: .semibold)) .foregroundStyle(AppDesign.textPrimary) if mediaItems.isEmpty { Text("暂无任务结果文件") .font(.system(size: AppMetrics.FontSize.subheadline)) .foregroundStyle(AppDesign.textSecondary) .frame(maxWidth: .infinity, alignment: .leading) .padding(.vertical, AppMetrics.Spacing.small) } else { LazyVGrid(columns: [GridItem(.adaptive(minimum: 92), spacing: AppMetrics.Spacing.small)], spacing: AppMetrics.Spacing.small) { ForEach(mediaItems) { media in TaskMediaThumbView(media: media) } } } } .padding(AppMetrics.Spacing.medium) .background(.white, in: RoundedRectangle(cornerRadius: AppMetrics.CornerRadius.card)) } /// 当前已加载的详情。 private var detail: TaskDetailResponse? { viewModel.detail } /// 展示任务名称。 private var displayName: String { detail?.name.nonEmpty ?? summary?.name.nonEmpty ?? "未命名任务" } /// 展示任务状态。 private var displayStatus: String { detail?.taskStatusLabel.nonEmpty ?? summary?.statusName.nonEmpty ?? "未知" } /// 展示任务媒体列表。 private var mediaItems: [TaskMediaItem] { if let detail { return detail.taskResult } return summary?.media ?? [] } } /// 任务信息行,展示左右结构的标题和值。 private struct TaskInfoRow: View { let title: String let value: String var body: some View { HStack(alignment: .top, spacing: AppMetrics.Spacing.medium) { Text(title) .font(.system(size: AppMetrics.FontSize.subheadline)) .foregroundStyle(AppDesign.textSecondary) .frame(width: 72, alignment: .leading) Text(value.isEmpty ? "暂无" : value) .font(.system(size: AppMetrics.FontSize.subheadline)) .foregroundStyle(AppDesign.textPrimary) .frame(maxWidth: .infinity, alignment: .leading) } } } /// 任务媒体缩略图视图,使用 Kingfisher 加载网络图片。 private struct TaskMediaThumbView: View { let media: TaskMediaItem var body: some View { ZStack(alignment: .bottomLeading) { RemoteImage(urlString: media.displayURL) { Image(systemName: media.isVideo ? "video.fill" : "photo") .font(.system(size: 28, weight: .semibold)) .foregroundStyle(AppDesign.placeholder) .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color(hex: 0xEEF2F7)) } .frame(height: 92) .clipShape(RoundedRectangle(cornerRadius: AppMetrics.CornerRadius.input)) if media.isVideo { Image(systemName: "play.circle.fill") .font(.system(size: 20, weight: .semibold)) .foregroundStyle(.white) .padding(AppMetrics.Spacing.xSmall) } } } } private extension String { /// 去除空白后返回非空字符串。 var nonEmpty: String? { let text = trimmingCharacters(in: .whitespacesAndNewlines) return text.isEmpty ? nil : text } }