主体:多语言支持(简体中文源 + 英/日/韩)
- 基础设施:Localizable.xcstrings(String Catalog,sourceLanguage=zh-Hans)
+ pbxproj developmentRegion/knownRegions 注册 en/ja/ko
- 全部硬编码 Locale("zh_CN") → Locale.current;中文 dateFormat → Date.FormatStyle(跟随系统)
- UI 中文字面量统一为 String(appLoc:)(显式绑定所选语言 bundle+locale,即时切换)
Text 字面量走环境 \.locale + Bundle 重定向
- 549 个 catalog key 全部 en/ja/ko 翻译完成(0 未翻译)
- App 内语言切换:我的 → 语言(LanguageManager + 即时生效,无需重启)
- 双用预设(症状/监测指标/慢病)本地化:static→computed 避免缓存
注:本提交为 WIP,一并打包了并行进行的功能模块
(HealthExport 健康导出、Security/Face ID 锁、DiaryAssist 日记 AI 辅助)
及 App 图标、CLAUDE.md、docs/scripts。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
181 lines
6.8 KiB
Swift
181 lines
6.8 KiB
Swift
import SwiftUI
|
||
|
||
struct A2ConfirmView: View {
|
||
var onSave: () -> Void
|
||
var onNext: () -> Void
|
||
var onBack: () -> Void
|
||
|
||
@State private var expanded = false
|
||
|
||
var body: some View {
|
||
VStack(spacing: 0) {
|
||
header
|
||
ScrollView(showsIndicators: false) {
|
||
VStack(alignment: .leading, spacing: 0) {
|
||
croppedPhoto.padding(.bottom, 14)
|
||
resultCard.padding(.bottom, 16)
|
||
actions
|
||
}
|
||
.padding(.horizontal, 18)
|
||
.padding(.bottom, 18)
|
||
}
|
||
}
|
||
.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)
|
||
}
|
||
Text("核对识别结果")
|
||
.font(.system(size: 15, weight: .semibold))
|
||
.foregroundStyle(Tj.Palette.text)
|
||
Spacer()
|
||
Text("识别用时 0.4s · 本地")
|
||
.font(.system(size: 10, design: .monospaced))
|
||
.foregroundStyle(Tj.Palette.text3)
|
||
.padding(.horizontal, 8)
|
||
.padding(.vertical, 4)
|
||
.background(Capsule().fill(Tj.Palette.sand2))
|
||
}
|
||
.padding(.horizontal, 12)
|
||
.padding(.top, 4)
|
||
.padding(.bottom, 8)
|
||
}
|
||
|
||
private var croppedPhoto: some View {
|
||
ZStack(alignment: .topTrailing) {
|
||
Text("低密度脂蛋白 3.84 mmol/L ↑")
|
||
.font(.system(size: 13, design: .monospaced))
|
||
.fontWeight(.semibold)
|
||
.tracking(0.3)
|
||
.foregroundStyle(Tj.Palette.text)
|
||
.padding(.vertical, 14)
|
||
.padding(.horizontal, 16)
|
||
.frame(maxWidth: .infinity, alignment: .leading)
|
||
.background(Color(red: 0.96, green: 0.93, blue: 0.87).opacity(0.92))
|
||
.clipShape(RoundedRectangle(cornerRadius: Tj.Radius.md, style: .continuous))
|
||
.shadow(color: Color(red: 0.196, green: 0.157, blue: 0.098).opacity(0.06),
|
||
radius: 2, x: 0, y: 1)
|
||
Text("已裁剪")
|
||
.font(.system(size: 9))
|
||
.tracking(0.5)
|
||
.foregroundStyle(Tj.Palette.text3)
|
||
.padding(.top, 8)
|
||
.padding(.trailing, 10)
|
||
}
|
||
}
|
||
|
||
private var resultCard: some View {
|
||
VStack(alignment: .leading, spacing: 14) {
|
||
HStack(alignment: .top) {
|
||
VStack(alignment: .leading, spacing: 4) {
|
||
Text("指标名 · 可编辑")
|
||
.font(.system(size: 11))
|
||
.foregroundStyle(Tj.Palette.text3)
|
||
Text("低密度脂蛋白胆固醇")
|
||
.font(.system(size: 19, weight: .semibold))
|
||
.foregroundStyle(Tj.Palette.text)
|
||
Text("LDL-C")
|
||
.font(.system(size: 12))
|
||
.foregroundStyle(Tj.Palette.text3)
|
||
}
|
||
Spacer()
|
||
TjBadge(text: String(appLoc: "偏高"), style: .brick)
|
||
}
|
||
|
||
HStack(spacing: 12) {
|
||
FieldBox(label: String(appLoc: "数值")) {
|
||
HStack(alignment: .firstTextBaseline, spacing: 4) {
|
||
Text("3.84")
|
||
.font(.system(size: 30, weight: .semibold))
|
||
.foregroundStyle(Tj.Palette.brick)
|
||
Text("mmol/L")
|
||
.font(.system(size: 11, design: .monospaced))
|
||
.foregroundStyle(Tj.Palette.text3)
|
||
}
|
||
}
|
||
FieldBox(label: String(appLoc: "参考范围")) {
|
||
HStack(alignment: .firstTextBaseline, spacing: 4) {
|
||
Text("< 3.40")
|
||
.font(.system(size: 14, design: .monospaced))
|
||
.foregroundStyle(Tj.Palette.text2)
|
||
Text("mmol/L")
|
||
.font(.system(size: 11, design: .monospaced))
|
||
.foregroundStyle(Tj.Palette.text3)
|
||
}
|
||
}
|
||
}
|
||
|
||
Button { withAnimation { expanded.toggle() } } label: {
|
||
HStack(alignment: .top, spacing: 10) {
|
||
RoundedRectangle(cornerRadius: 2, style: .continuous)
|
||
.fill(Tj.Palette.brick)
|
||
.frame(width: 4)
|
||
Text(expanded
|
||
? "超过参考上限 0.44,属轻度偏高。建议关注饮食结构(减少动物脂肪摄入),3 个月内复查。若家族有心血管病史,可与医生沟通是否需要药物干预。"
|
||
: "超过参考上限 0.44,属轻度偏高。点击展开详细解读 ›")
|
||
.font(.system(size: 12))
|
||
.foregroundStyle(Tj.Palette.text2)
|
||
.lineSpacing(5)
|
||
.multilineTextAlignment(.leading)
|
||
.frame(maxWidth: .infinity, alignment: .leading)
|
||
}
|
||
.padding(12)
|
||
.background(
|
||
RoundedRectangle(cornerRadius: Tj.Radius.sm, style: .continuous)
|
||
.fill(Tj.Palette.sand)
|
||
)
|
||
}
|
||
.buttonStyle(.plain)
|
||
}
|
||
.padding(18)
|
||
.tjCard()
|
||
}
|
||
|
||
private var actions: some View {
|
||
VStack(spacing: 10) {
|
||
Button(action: onSave) {
|
||
Text("保存到记录")
|
||
.frame(maxWidth: .infinity)
|
||
}
|
||
.buttonStyle(TjPrimaryButton())
|
||
|
||
Button(action: onNext) {
|
||
HStack(spacing: 8) {
|
||
Image(systemName: "camera.fill").font(.system(size: 14))
|
||
Text("继续拍下一项")
|
||
}
|
||
.frame(maxWidth: .infinity)
|
||
}
|
||
.buttonStyle(TjGhostButton())
|
||
}
|
||
}
|
||
}
|
||
|
||
private struct FieldBox<Content: View>: View {
|
||
let label: String
|
||
@ViewBuilder var content: Content
|
||
|
||
var body: some View {
|
||
VStack(alignment: .leading, spacing: 4) {
|
||
Text(label)
|
||
.font(.system(size: 10))
|
||
.tracking(0.5)
|
||
.foregroundStyle(Tj.Palette.text3)
|
||
content
|
||
}
|
||
.frame(maxWidth: .infinity, alignment: .leading)
|
||
.padding(.vertical, 10)
|
||
.padding(.horizontal, 12)
|
||
.overlay(
|
||
RoundedRectangle(cornerRadius: Tj.Radius.sm, style: .continuous)
|
||
.strokeBorder(Tj.Palette.lineSoft, lineWidth: 1)
|
||
)
|
||
}
|
||
}
|