import SwiftUI import SwiftData /// 单条「导出身体档案」详情。只读 Markdown + 复制 / 分享 / 删除。 struct HealthExportDetailView: View { @Environment(\.modelContext) private var ctx @Environment(\.dismiss) private var dismiss let export: HealthExport @State private var copiedFlash: Bool = false @State private var showDeleteConfirm = false var body: some View { VStack(spacing: 0) { header ScrollView { VStack(alignment: .leading, spacing: 16) { metaBar promptBlock MarkdownView(text: export.content) .padding(16) .frame(maxWidth: .infinity, alignment: .leading) .background( RoundedRectangle(cornerRadius: Tj.Radius.md, style: .continuous) .fill(Tj.Palette.paper) ) .overlay( RoundedRectangle(cornerRadius: Tj.Radius.md, style: .continuous) .strokeBorder(Tj.Palette.lineSoft, lineWidth: 1) ) } .padding(.horizontal, 20) .padding(.vertical, 16) } actionRow } .background(Tj.Palette.sand.ignoresSafeArea()) .alert("永久删除这份导出?", isPresented: $showDeleteConfirm) { Button("删除", role: .destructive) { ctx.delete(export) try? ctx.save() dismiss() } Button("取消", role: .cancel) {} } message: { Text("删除后无法恢复。源记录(指标、症状等)不受影响。") } } private var header: some View { HStack(alignment: .center, spacing: 12) { Button { dismiss() } label: { Image(systemName: "xmark") .font(.system(size: 16, weight: .semibold)) .foregroundStyle(Tj.Palette.text) .frame(width: 32, height: 32) .background(Circle().fill(Tj.Palette.sand2)) } VStack(alignment: .leading, spacing: 2) { Text("身体档案 · 历史导出") .font(.tjH2()) .foregroundStyle(Tj.Palette.text) Text(Self.absoluteDate(export.createdAt)) .font(.system(size: 11)) .foregroundStyle(Tj.Palette.text3) } Spacer() TjLockChip() } .padding(.horizontal, 20) .padding(.vertical, 14) .background(Tj.Palette.sand) .overlay(alignment: .bottom) { Rectangle().fill(Tj.Palette.lineSoft).frame(height: 1) } } private var metaBar: some View { HStack(spacing: 10) { TjBadge(text: export.modelTag, style: .neutral) if export.decodeRate > 0 { Text(String(format: "%.1f tok/s", export.decodeRate)) .font(.system(size: 11, design: .monospaced)) .foregroundStyle(Tj.Palette.leaf) } Spacer() if let from = export.inferredTimeFromDate, let to = export.inferredTimeToDate { Text("\(Self.shortDate(from)) — \(Self.shortDate(to))") .font(.system(size: 11, design: .monospaced)) .foregroundStyle(Tj.Palette.text3) } } } private var promptBlock: some View { HStack(alignment: .top, spacing: 8) { Image(systemName: "quote.opening") .font(.system(size: 12)) .foregroundStyle(Tj.Palette.text3) Text(export.prompt) .font(.system(size: 13)) .foregroundStyle(Tj.Palette.text2) } .padding(12) .frame(maxWidth: .infinity, alignment: .leading) .background( RoundedRectangle(cornerRadius: Tj.Radius.sm, style: .continuous) .fill(Tj.Palette.sand2) ) } private var actionRow: some View { HStack(spacing: 10) { Button { copy() } label: { Label(copiedFlash ? "已复制" : "复制", systemImage: copiedFlash ? "checkmark" : "doc.on.doc") } .buttonStyle(TjGhostButton(height: 44, fontSize: 13, horizontalPadding: 14)) ShareLink(item: export.content) { Label("分享", systemImage: "square.and.arrow.up") .font(.system(size: 13, weight: .semibold)) .tracking(1) .foregroundStyle(Tj.Palette.ink) .padding(.horizontal, 14) .frame(height: 44) .background(Capsule().strokeBorder(Tj.Palette.ink, lineWidth: 1)) .contentShape(Capsule()) // 纯描边胶囊:内边距区也可点 } Spacer() Button(role: .destructive) { showDeleteConfirm = true } label: { Image(systemName: "trash") .font(.system(size: 15, weight: .medium)) .foregroundStyle(Tj.Palette.brick) .frame(width: 44, height: 44) .background(Circle().strokeBorder(Tj.Palette.brick.opacity(0.4), lineWidth: 1)) } } .padding(.horizontal, 20) .padding(.vertical, 12) .background(Tj.Palette.paper) .overlay(alignment: .top) { Rectangle().fill(Tj.Palette.lineSoft).frame(height: 1) } } private func copy() { UIPasteboard.general.string = export.content copiedFlash = true DispatchQueue.main.asyncAfter(deadline: .now() + 1.4) { copiedFlash = false } } private static func absoluteDate(_ d: Date) -> String { d.formatted(.dateTime.year().month().day().hour().minute()) } private static func shortDate(_ d: Date) -> String { let f = DateFormatter() f.locale = Locale(identifier: "en_US_POSIX") f.dateFormat = "MM-dd" return f.string(from: d) } } #Preview { let exp = HealthExport( prompt: "我感冒3天了,把最近一个月的健康情况给医生看", content: """ # 就诊摘要 — 感冒就诊 ## 主诉 患者男,38 岁,感冒 3 天未愈。 ## 患者背景 - 高血压 2 年 - 在服药:缬沙坦 80mg qd """, inferredTimeFromDate: Calendar.current.date(byAdding: .day, value: -30, to: .now), inferredTimeToDate: .now, inferredIntent: "cold_consult", decodeRate: 24.3 ) return HealthExportDetailView(export: exp) }