import SwiftUI struct B5IndicatorData { let name: String let value: String let unit: String let range: String let status: IndicatorStatus let note: String? } struct B5ResultView: View { var onSave: () -> Void var onBack: () -> Void @State private var expandedIndex: Int? = 0 @State private var normalsExpanded = false let abnormal: [B5IndicatorData] = [ .init(name: String(appLoc: "低密度脂蛋白胆固醇"), value: "3.84", unit: "mmol/L", range: "< 3.40", status: .high, note: String(appLoc: "超过参考上限 0.44。建议关注饮食结构,3 个月内复查。")), .init(name: String(appLoc: "甘油三酯 TG"), value: "1.78", unit: "mmol/L", range: "0.45–1.70", status: .high, note: nil), .init(name: String(appLoc: "尿酸 UA"), value: "428", unit: "μmol/L", range: "150–420", status: .high, note: nil), .init(name: String(appLoc: "维生素 D"), value: "18", unit: "ng/mL", range: "30–100", status: .low, note: nil), ] let normalCount = 24 var body: some View { VStack(spacing: 0) { header ScrollView(showsIndicators: false) { VStack(alignment: .leading, spacing: 0) { reportMeta.padding(.bottom, 16) summaryCard.padding(.bottom, 18) SectionLabel(String(appLoc: "异常项"), count: abnormal.count, accent: .brick) .padding(.bottom, 10) VStack(spacing: 8) { ForEach(Array(abnormal.enumerated()), id: \.offset) { idx, it in IndicatorRow(item: it, expanded: expandedIndex == idx) { withAnimation { expandedIndex = (expandedIndex == idx) ? nil : idx } } } } .padding(.bottom, 18) SectionLabel(String(appLoc: "正常项"), count: normalCount, accent: .leaf) .padding(.bottom, 10) normalCollapsed } .padding(.horizontal, 18) .padding(.bottom, 16) } HStack(spacing: 10) { Button(action: onSave) { Text("保存归档").frame(maxWidth: .infinity) } .buttonStyle(TjPrimaryButton()) Button { } label: { Image(systemName: "square.and.arrow.up") .font(.system(size: 16, weight: .semibold)) } .buttonStyle(TjGhostButton(horizontalPadding: 16)) } .padding(.horizontal, 18) .padding(.bottom, 14) .padding(.top, 10) } .background(Tj.Palette.sand.ignoresSafeArea()) } private var header: some View { HStack(spacing: 6) { Button(action: onBack) { Image(systemName: "chevron.left") .font(.system(size: 18, weight: .semibold)) .foregroundStyle(Tj.Palette.text) .frame(width: 36, height: 36) } Spacer() Button { } label: { HStack(spacing: 4) { Image(systemName: "photo") Text("查看原图") } .font(.system(size: 12)) .foregroundStyle(Tj.Palette.text3) .padding(8) } } .padding(.horizontal, 12) .padding(.top, 4) } private var reportMeta: some View { VStack(alignment: .leading, spacing: 6) { HStack(spacing: 8) { TjBadge(text: String(appLoc: "体检报告"), style: .ink) Text("3 页") .font(.system(size: 11)) .foregroundStyle(Tj.Palette.text3) Spacer() TjLockChip() } Text("2026 春季年度体检") .font(.system(size: 22, weight: .bold)) .foregroundStyle(Tj.Palette.text) Text("2026 / 05 / 25 · 协和医院体检中心") .font(.system(size: 12)) .foregroundStyle(Tj.Palette.text3) } } private var summaryCard: some View { VStack(alignment: .leading, spacing: 0) { HStack(spacing: 10) { Text("整体摘记") .font(.system(size: 12, weight: .semibold)) .tracking(0.3) .foregroundStyle(Tj.Palette.brick) .fixedSize() Rectangle().fill(Tj.Palette.line).frame(height: 1) Text("本机摘要") .font(.system(size: 11)) .foregroundStyle(Tj.Palette.text3) .fixedSize() } .padding(.bottom, 12) HStack(spacing: 14) { Stat(n: "28", label: String(appLoc: "总项")) Stat(n: "3", label: String(appLoc: "偏高"), tone: .brick) Stat(n: "1", label: String(appLoc: "偏低"), tone: .amber) Stat(n: "24", label: String(appLoc: "正常"), tone: .leaf) } .padding(.bottom, 14) Text("本次共检测 28 项,\(Text("3 项偏高").fontWeight(.semibold).underline(color: Tj.Palette.brick))(血脂相关 2 项 + 尿酸)、\(Text("1 项偏低").fontWeight(.semibold).underline(color: Tj.Palette.amber))(维生素 D)。整体趋势提示代谢风险有所抬升,建议优化饮食并复查血脂。") .font(.system(size: 14)) .foregroundStyle(Tj.Palette.text) .lineSpacing(6) .padding(.bottom, 12) TjDashedDivider().padding(.bottom, 10) Text("仅供参考,不构成医疗建议") .font(.system(size: 11)) .italic() .foregroundStyle(Tj.Palette.text3) } .padding(.leading, 20) .padding(.trailing, 20) .padding(.vertical, 20) .frame(maxWidth: .infinity, alignment: .leading) .background( Tj.Palette.paper .overlay(alignment: .leading) { Tj.Palette.brick.frame(width: 3) } ) .clipShape(RoundedRectangle(cornerRadius: 2, style: .continuous)) .shadow(color: Color(red: 0.196, green: 0.157, blue: 0.098).opacity(0.06), radius: 0, x: 0, y: 1) } private var normalCollapsed: some View { Button { withAnimation { normalsExpanded.toggle() } } label: { HStack(spacing: 10) { TjBadge(text: "\(normalCount)", style: .leaf) Text("谷丙转氨酶、空腹血糖、糖化血红蛋白…") .font(.system(size: 13)) .foregroundStyle(Tj.Palette.text2) .lineLimit(1) Spacer() Image(systemName: normalsExpanded ? "chevron.up" : "chevron.down") .font(.system(size: 12, weight: .medium)) .foregroundStyle(Tj.Palette.text3) } .padding(.horizontal, 16) .padding(.vertical, 14) .tjCard(bordered: true) } .buttonStyle(.plain) } } private struct Stat: View { let n: String let label: String var tone: Tone = .ink enum Tone { case ink, brick, amber, leaf } var color: Color { switch tone { case .ink: return Tj.Palette.text case .brick: return Tj.Palette.brick case .amber: return Color(red: 0.59, green: 0.45, blue: 0.27) case .leaf: return Tj.Palette.leaf } } var body: some View { VStack(alignment: .leading, spacing: 4) { Text(n) .font(.system(size: 24, weight: .semibold)) .foregroundStyle(color) Text(label) .font(.system(size: 10)) .tracking(0.5) .foregroundStyle(Tj.Palette.text3) } .frame(maxWidth: .infinity, alignment: .leading) } } private struct SectionLabel: View { let title: String let count: Int let accent: AccentKind enum AccentKind { case brick, leaf } init(_ title: String, count: Int, accent: AccentKind) { self.title = title self.count = count self.accent = accent } var body: some View { HStack(spacing: 8) { RoundedRectangle(cornerRadius: 2, style: .continuous) .fill(accent == .brick ? Tj.Palette.brick : Tj.Palette.leaf) .frame(width: 4, height: 14) Text(title).font(.system(size: 13, weight: .semibold)).foregroundStyle(Tj.Palette.text) Text("· \(count)").font(.system(size: 11)).foregroundStyle(Tj.Palette.text3) } } } private struct IndicatorRow: View { let item: B5IndicatorData let expanded: Bool let onTap: () -> Void var statusBadge: TjBadgeStyle { switch item.status { case .high: return .brick case .low: return .amber case .normal: return .leaf } } var statusWord: String { switch item.status { case .high: return String(appLoc: "偏高") case .low: return String(appLoc: "偏低") case .normal: return String(appLoc: "正常") } } var valueColor: Color { switch item.status { case .high: return Tj.Palette.brick case .low: return Color(red: 0.55, green: 0.45, blue: 0.32) case .normal: return Tj.Palette.text } } var body: some View { Button(action: onTap) { VStack(alignment: .leading, spacing: 10) { HStack(alignment: .top, spacing: 12) { VStack(alignment: .leading, spacing: 4) { HStack(spacing: 8) { Text(item.name) .font(.system(size: 14, weight: .semibold)) .foregroundStyle(Tj.Palette.text) .lineLimit(1) TjBadge(text: statusWord, style: statusBadge) } Text("范围 \(item.range) \(item.unit)") .font(.system(size: 11, design: .monospaced)) .foregroundStyle(Tj.Palette.text3) } Spacer(minLength: 8) VStack(alignment: .trailing, spacing: 2) { Text(item.value) .font(.system(size: 22, weight: .semibold)) .foregroundStyle(valueColor) Text(item.unit) .font(.system(size: 10, design: .monospaced)) .foregroundStyle(Tj.Palette.text3) } } if expanded, let note = item.note { TjDashedDivider() Text(note) .font(.system(size: 12)) .foregroundStyle(Tj.Palette.text2) .lineSpacing(5) .frame(maxWidth: .infinity, alignment: .leading) } } .padding(.horizontal, 16) .padding(.vertical, 14) .background( RoundedRectangle(cornerRadius: Tj.Radius.md, style: .continuous) .fill(Tj.Palette.paper) ) .overlay( RoundedRectangle(cornerRadius: Tj.Radius.md, style: .continuous) .strokeBorder( item.status != .normal ? Color(red: 0.78, green: 0.68, blue: 0.48).opacity(0.5) : Tj.Palette.lineSoft, lineWidth: 1 ) ) } .buttonStyle(.plain) } }