```
refactor: 重命名项目名称从"体己"到"康康" 将整个项目的目录结构从"体己"重命名为"康康",包括所有源代码文件、 资源文件、测试文件以及Xcode项目配置文件。此更改涉及项目中所有的 文件路径和应用入口点(App/TijiApp.swift → App/KangkangApp.swift)。 ```
This commit is contained in:
323
康康/Features/Archive/B5ResultView.swift
Normal file
323
康康/Features/Archive/B5ResultView.swift
Normal file
@@ -0,0 +1,323 @@
|
||||
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: "低密度脂蛋白胆固醇", value: "3.84", unit: "mmol/L", range: "< 3.40", status: .high,
|
||||
note: "超过参考上限 0.44。建议关注饮食结构,3 个月内复查。"),
|
||||
.init(name: "甘油三酯 TG", value: "1.78", unit: "mmol/L", range: "0.45–1.70", status: .high, note: nil),
|
||||
.init(name: "尿酸 UA", value: "428", unit: "μmol/L", range: "150–420", status: .high, note: nil),
|
||||
.init(name: "维生素 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("异常项", 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("正常项", 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: "体检报告", 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: "总项")
|
||||
Stat(n: "3", label: "偏高", tone: .brick)
|
||||
Stat(n: "1", label: "偏低", tone: .amber)
|
||||
Stat(n: "24", label: "正常", 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 "偏高"
|
||||
case .low: return "偏低"
|
||||
case .normal: return "正常"
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user