diff --git a/AIStudyApp/AIStudyApp/AIStudyAppApp.swift b/AIStudyApp/AIStudyApp/AIStudyAppApp.swift index 0a5a59e..12379eb 100644 --- a/AIStudyApp/AIStudyApp/AIStudyAppApp.swift +++ b/AIStudyApp/AIStudyApp/AIStudyAppApp.swift @@ -1,17 +1,186 @@ // -// AIStudyAppApp.swift -// AIStudyApp -// -// Created by DSR on 2026/5/4. +// AIStudyAppApp.swift - 根路由:引导流程 vs 主界面 // import SwiftUI @main struct AIStudyAppApp: App { + @AppStorage("hasCompletedOnboarding") private var hasCompletedOnboarding = false + var body: some Scene { WindowGroup { - ContentView() + if hasCompletedOnboarding { + ContentView() + .preferredColorScheme(.dark) + } else { + OnboardingFlowView(hasCompletedOnboarding: $hasCompletedOnboarding) + .preferredColorScheme(.dark) + } + } + } +} + +// MARK: - Onboarding Flow (Splash → Welcome → Login → Onboarding → GoalSetup) +// 对应 React: SplashPage, WelcomePage, LoginPage, OnboardingPage, GoalSetupPage + +struct OnboardingFlowView: View { + @Binding var hasCompletedOnboarding: Bool + @State private var step = 0 + + var body: some View { + ZStack { + switch step { + case 0: SplashPage { withAnimation(.easeInOut(duration: 0.5)) { step = 1 } } + case 1: WelcomePage { withAnimation { step = 2 } } onSkip: { hasCompletedOnboarding = true } + case 2: LoginPage { step = 3 } onSkip: { hasCompletedOnboarding = true } + case 3: OnboardingPage { step = 4 } + case 4: GoalSetupPage { $0 ? (hasCompletedOnboarding = true) : (step = 0) } + default: EmptyView() + } + } + .preferredColorScheme(.dark) + } +} + +// Splash +struct SplashPage: View { + let onFinish: () -> Void + var body: some View { + ZStack { + LinearGradient(colors: [Color(hex: "#0D0D20"), Color(hex: "#0F0F1A"), Color(hex: "#130D20")], startPoint: .top, endPoint: .bottom).ignoresSafeArea() + Circle().fill(RadialGradient(colors: [Color(hex: "#7C6EFA", opacity: 0.25), .clear], center: .center, startRadius: 0, endRadius: 140)).frame(width: 280, height: 280).offset(y: -60).allowsHitTesting(false) + Circle().fill(RadialGradient(colors: [Color(hex: "#F97316", opacity: 0.15), .clear], center: .center, startRadius: 0, endRadius: 100)).frame(width: 200, height: 200).offset(y: 180).allowsHitTesting(false) + VStack(spacing: 0) { + RoundedRectangle(cornerRadius: 28) + .fill(LinearGradient(colors: [Color(hex: "#7C6EFA"), Color(hex: "#A78BFA"), Color(hex: "#F97316")], startPoint: .topLeading, endPoint: .bottomTrailing)) + .frame(width: 96, height: 96) + .overlay(Image(systemName: "brain.head.profile").font(.system(size: 44)).foregroundColor(.white.opacity(0.8))) + .shadow(color: Color(hex: "#7C6EFA", opacity: 0.5), radius: 40) + .padding(.bottom, 24) + Text("知习") + .font(.system(size: 36, weight: .heavy)).tracking(-1) + .foregroundStyle(LinearGradient(colors: [Color(hex: "#A78BFA"), Color(hex: "#F0F0FF"), Color(hex: "#F97316")], startPoint: .leading, endPoint: .trailing)) + Text("Z H I X I").font(.system(size: 13, weight: .medium)).foregroundColor(Color(hex: "#F0F0FF", opacity: 0.4)).tracking(3).padding(.top, 6) + Text("AI-first 系统化学习").font(.system(size: 14)).foregroundColor(Color(hex: "#F0F0FF", opacity: 0.45)).tracking(0.5).padding(.top, 24) + } + VStack { Spacer() + ZStack(alignment: .leading) { RoundedRectangle(cornerRadius: 2).fill(Color(hex: "#FFFFFF", opacity: 0.1)).frame(width: 40, height: 3); RoundedRectangle(cornerRadius: 2).fill(LinearGradient(colors: [.zxPurple, Color.zxOrange], startPoint: .leading, endPoint: .trailing)).frame(width: 24, height: 3) } + .padding(.bottom, 80) + } + } + .onAppear { DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { onFinish() } } + } +} + +// Welcome +struct WelcomePage: View { + let onContinue: () -> Void; let onSkip: () -> Void + var body: some View { + ZStack { + ZXGradient.page.ignoresSafeArea() + Circle().fill(RadialGradient(colors: [Color(hex: "#7C6EFA", opacity: 0.12), .clear], center: .topTrailing, startRadius: 0, endRadius: 260)).frame(width: 260, height: 260).offset(x: 80, y: -120).allowsHitTesting(false) + VStack { Spacer() + VStack(spacing: 14) { + HStack(spacing: 6) { Image(systemName: "sparkles").font(.system(size: 12)); Text("AI 驱动").font(.system(size: 12, weight: .semibold)) } + .foregroundColor(Color.zxAccent).padding(.horizontal, 12).padding(.vertical, 6).background(Color(hex: "#7C6EFA", opacity: 0.1)).clipShape(Capsule()) + Text("用 AI 重新定义\n你的学习方式").font(.system(size: 32, weight: .heavy)).tracking(-0.8).lineSpacing(4) + VStack(spacing: 10) { FeatureRow(icon: "🧠", title: "主动回忆", desc: "基于间隔重复的智能复习"); FeatureRow(icon: "🎤", title: "费曼解释", desc: "用自己的话讲出来"); FeatureRow(icon: "📊", title: "AI 分析", desc: "发现知识薄弱点") } + } + VStack(spacing: 12) { Button { onContinue() } label: { Text("开始使用").font(.system(size: 16, weight: .bold)).foregroundColor(.white).frame(maxWidth: .infinity).frame(height: 56).background(ZXGradient.ctaButton).clipShape(RoundedRectangle(cornerRadius: 18)).shadow(color: Color(hex: "#7C6EFA", opacity: 0.4), radius: 20) }; Button { onSkip() } label: { Text("已有账号?立即登录").font(.system(size: 14, weight: .medium)).foregroundColor(Color(hex: "#F0F0FF", opacity: 0.7)) }.padding(.bottom, 32) } + }.padding(.horizontal, 20) + } + } +} +struct FeatureRow: View { let icon: String; let title: String; let desc: String + var body: some View { HStack(spacing: 14) { Text(icon).font(.system(size: 20)).frame(width: 40, height: 40).background(Color(hex: "#7C6EFA", opacity: 0.1)).clipShape(RoundedRectangle(cornerRadius: 12)); VStack(alignment: .leading, spacing: 2) { Text(title).font(.system(size: 14, weight: .semibold)).foregroundColor(Color.zxF0); Text(desc).font(.system(size: 12)).foregroundColor(Color.zxF04) } }.padding(.horizontal, 16).padding(.vertical, 14).background(Color.zxFill003).overlay(RoundedRectangle(cornerRadius: 16).stroke(Color.zxBorder006, lineWidth: 1)).clipShape(RoundedRectangle(cornerRadius: 16)) } +} + +// Login +struct LoginPage: View { + let onContinue: () -> Void; let onSkip: () -> Void + @State private var isEmail = false; @State private var phone = ""; @State private var email = ""; @State private var pw = ""; @State private var showPw = false + var body: some View { + ZStack { + Color.zxBg0.ignoresSafeArea() + Circle().fill(RadialGradient(colors: [Color(hex: "#7C6EFA", opacity: 0.1), .clear], center: .top, startRadius: 0, endRadius: 200)).frame(width: 200, height: 200).offset(y: -60).allowsHitTesting(false) + VStack { Spacer() + VStack(spacing: 24) { + VStack(spacing: 6) { Text("欢迎登录").font(.system(size: 28, weight: .heavy)).tracking(-0.6); Text("使用手机号或邮箱登录").font(.system(size: 14)).foregroundColor(Color.zxF05) } + HStack(spacing: 4) { tabBtn("手机号", !isEmail) { isEmail = false }; tabBtn("邮箱", isEmail) { isEmail = true } }.padding(4).background(Color.zxFill004).clipShape(RoundedRectangle(cornerRadius: 12)) + if isEmail { + VStack(alignment: .leading, spacing: 8) { + Text("邮箱").font(.system(size: 12, weight: .semibold)).foregroundColor(Color.zxF035).tracking(0.5) + ZXInputField(placeholder: "your@email.com", text: $email) + } + } else { + VStack(alignment: .leading, spacing: 8) { + Text("手机号").font(.system(size: 12, weight: .semibold)).foregroundColor(Color.zxF035).tracking(0.5) + HStack(spacing: 0) { Text("+86").font(.system(size: 14, weight: .semibold)).foregroundColor(Color.zxF0).padding(.trailing, 12).overlay(alignment: .trailing) { Rectangle().fill(Color.zxBorder01).frame(width: 1).padding(.vertical, 4) }.padding(.trailing, 12); TextField("手机号", text: $phone).keyboardType(.phonePad).font(.system(size: 15)).tint(Color.zxPurple) }.padding(.horizontal, 16).frame(height: 52).background(Color.zxFill004).overlay(RoundedRectangle(cornerRadius: 14).stroke(Color.zxBorder008, lineWidth: 1)).clipShape(RoundedRectangle(cornerRadius: 14)) + } + } + ZXInputField(placeholder: "密码", text: $pw, isSecure: !showPw) + HStack { Spacer(); Button { showPw.toggle() } label: { Image(systemName: showPw ? "eye" : "eye.slash").font(.system(size: 16)).foregroundColor(Color.zxF03) } }.padding(.trailing, 4) + HStack { Spacer(); Button("忘记密码?") {}.font(.system(size: 13)).foregroundColor(Color.zxPurple) } + Button { onContinue() } label: { Text("登录").font(.system(size: 16, weight: .bold)).foregroundColor(.white).frame(maxWidth: .infinity).frame(height: 56).background(ZXGradient.ctaButton).clipShape(RoundedRectangle(cornerRadius: 18)).shadow(color: Color(hex: "#7C6EFA", opacity: 0.4), radius: 20) } + HStack(spacing: 12) { Rectangle().fill(Color.zxBorder008).frame(height: 1); Text("或").font(.system(size: 12)).foregroundColor(Color.zxF03); Rectangle().fill(Color.zxBorder008).frame(height: 1) } + HStack(spacing: 12) { SocialLoginBtn(emoji: "💬", text: "微信登陆", color: .green) {}; SocialLoginBtn(emoji: "🍎", text: "Apple 登录", color: .white) {} } + }.padding(.horizontal, 20).padding(.bottom, 32) + } + } + } + func tabBtn(_ t: String, _ active: Bool, _ a: @escaping () -> Void) -> some View { Button(action: a) { Text(t).font(.system(size: 13, weight: .semibold)).foregroundColor(active ? .white : Color.zxF05).frame(maxWidth: .infinity).frame(height: 36).background(active ? AnyView(ZXGradient.brand) : AnyView(Color.clear)).clipShape(RoundedRectangle(cornerRadius: 9)) } } +} +struct ZXInputField: View { let placeholder: String; @Binding var text: String; var isSecure = false + var body: some View { HStack { if isSecure { SecureField(placeholder, text: $text) } else { TextField(placeholder, text: $text) } }.font(.system(size: 15)).tint(Color.zxPurple).padding(.horizontal, 16).frame(height: 52).background(Color.zxFill004).overlay(RoundedRectangle(cornerRadius: 14).stroke(Color.zxBorder008, lineWidth: 1)).clipShape(RoundedRectangle(cornerRadius: 14)) } +} +struct SocialLoginBtn: View { let emoji: String; let text: String; let color: Color; let action: () -> Void + var body: some View { Button(action: action) { HStack(spacing: 10) { Text(emoji).font(.system(size: 18)); Text(text).font(.system(size: 11, weight: .medium)) }.foregroundColor(Color.zxF007).frame(maxWidth: .infinity).frame(height: 52).background(Color.zxFill004).overlay(RoundedRectangle(cornerRadius: 14).stroke(Color.zxBorder008, lineWidth: 1)).clipShape(RoundedRectangle(cornerRadius: 14)) } } +} + +// Onboarding +struct OnboardingPage: View { + let onContinue: () -> Void + @State private var step = 0 + let titles = ["输入知识", "主动输出", "AI 分析", "掌握知识"] + let descs = ["从任何地方收集并导入学习资料,构建你的专属知识库。", "通过间隔回忆和费曼解释法,将知识转化为长期记忆。", "AI 自动定位薄弱知识点,给出针对性的学习建议。", "系统性掌握每一个知识点,建立牢固的知识体系。"] + let icons = ["square.and.arrow.down", "brain.head.profile", "sparkle.magnifyingglass", "chart.line.uptrend.xyaxis"] + var body: some View { + ZStack { ZXGradient.page.ignoresSafeArea() + VStack(spacing: 0) { Spacer() + HStack(spacing: 6) { ForEach(0..<4, id: \.self) { i in RoundedRectangle(cornerRadius: 2).fill(i == step ? AnyShapeStyle(ZXGradient.brand) : AnyShapeStyle(Color(hex: "#FFFFFF", opacity: 0.1))).frame(width: i == step ? 24 : 8, height: 4) } } + VStack(spacing: 12) { Text(titles[step]).font(.system(size: 24, weight: .heavy)).tracking(-0.5); Text(descs[step]).font(.system(size: 14)).foregroundColor(Color.zxF04).lineSpacing(4).multilineTextAlignment(.center) }.padding(.top, 32).padding(.bottom, 40) + Button { if step < 3 { withAnimation { step += 1 } } else { onContinue() } } label: { Text(step < 3 ? "下一步" : "开始使用").font(.system(size: 16, weight: .bold)).foregroundColor(.white).frame(maxWidth: .infinity).frame(height: 56).background(ZXGradient.ctaButton).clipShape(RoundedRectangle(cornerRadius: 18)).shadow(color: Color(hex: "#7C6EFA", opacity: 0.4), radius: 20) } + Button("跳过") { onContinue() }.font(.system(size: 12)).foregroundColor(Color.zxF03).padding(.top, 12).padding(.bottom, 32) + }.padding(.horizontal, 20) + } + } +} +struct GoalSetupPage: View { + let onComplete: (Bool) -> Void // true = launch app + @State private var selectedGoal = "" + let goals = [("🧑‍🎓","备考考试","公考、考研、考证等"),("💼","职业技能","编程、设计、产品等"),("📚","通识学习","扩充知识面"),("🎯","自定义","设定自己的目标")] + @State private var selectedMethod = "" + let methods = ["间隔回忆","费曼技巧","AI 分析"] + @State private var dailyMins = "30 分钟" + let times = ["15 分钟","30 分钟","1 小时","不限制"] + var body: some View { + ZStack { ZXGradient.page.ignoresSafeArea() + VStack(spacing: 0) { Spacer() + Text("设定你的学习目标").font(.system(size: 24, weight: .heavy)).tracking(-0.5).foregroundColor(Color.zxF0).padding(.bottom, 24) + ScrollView { VStack(spacing: 16) { + VStack(alignment: .leading, spacing: 10) { Text("学习目标").font(.system(size: 12, weight: .semibold)).foregroundColor(Color.zxF035).tracking(0.5) + ForEach(goals, id: \.1) { g in let sel = selectedGoal == g.1; Button { selectedGoal = g.1 } label: { HStack(spacing: 12) { Text(g.0).font(.system(size: 22)).frame(width: 44, height: 44).background(sel ? Color(hex: "#7C6EFA", opacity: 0.15) : Color.zxFill005).clipShape(RoundedRectangle(cornerRadius: 12)); VStack(alignment: .leading, spacing: 2) { Text(g.1).font(.system(size: 15, weight: .semibold)).foregroundColor(sel ? Color.zxPurple : Color.zxF0); Text(g.2).font(.system(size: 12)).foregroundColor(Color.zxF04) }; Spacer(); Circle().stroke(sel ? Color.zxPurple : Color(hex: "#FFFFFF", opacity: 0.2), lineWidth: 2).frame(width: 22, height: 22).overlay { if sel { Circle().fill(Color.zxPurple).frame(width: 12, height: 12) } } }.padding(14).background(sel ? Color(hex: "#7C6EFA", opacity: 0.08) : Color.zxFill003).overlay(RoundedRectangle(cornerRadius: 16).stroke(sel ? Color(hex: "#7C6EFA", opacity: 0.25) : Color.zxBorder006, lineWidth: 1)).clipShape(RoundedRectangle(cornerRadius: 16)) }.foregroundColor(.primary) } + } + VStack(alignment: .leading, spacing: 10) { Text("学习方法").font(.system(size: 12, weight: .semibold)).foregroundColor(Color.zxF035).tracking(0.5) + HStack(spacing: 8) { ForEach(methods, id: \.self) { m in let sel = selectedMethod == m; Button { selectedMethod = m } label: { Text(m).font(.system(size: 13)).fontWeight(sel ? .semibold : .regular).foregroundColor(sel ? Color.zxPurple : Color.zxF05).padding(.horizontal, 16).padding(.vertical, 10).background(sel ? Color(hex: "#7C6EFA", opacity: 0.1) : Color.zxFill003).overlay(RoundedRectangle(cornerRadius: 20).stroke(sel ? Color(hex: "#7C6EFA", opacity: 0.25) : Color.zxBorder006, lineWidth: 1)).clipShape(RoundedRectangle(cornerRadius: 20)) }.foregroundColor(.primary) } } + } + VStack(alignment: .leading, spacing: 10) { Text("每日学习时间").font(.system(size: 12, weight: .semibold)).foregroundColor(Color.zxF035).tracking(0.5) + HStack(spacing: 8) { ForEach(times, id: \.self) { t in let sel = dailyMins == t; Button { dailyMins = t } label: { Text(t).font(.system(size: 12)).fontWeight(sel ? .semibold : .regular).foregroundColor(sel ? Color.zxPurple : Color.zxF05).frame(maxWidth: .infinity).frame(height: 40).background(sel ? Color(hex: "#7C6EFA", opacity: 0.1) : Color.zxFill003).overlay(RoundedRectangle(cornerRadius: 12).stroke(sel ? Color(hex: "#7C6EFA", opacity: 0.25) : Color.zxBorder006, lineWidth: 1)).clipShape(RoundedRectangle(cornerRadius: 12)) }.foregroundColor(.primary) } } + } + } }.padding(.horizontal, 20) + Button { onComplete(true) } label: { Text("开始学习").font(.system(size: 16, weight: .bold)).foregroundColor(.white).frame(maxWidth: .infinity).frame(height: 56).background(ZXGradient.ctaButton).clipShape(RoundedRectangle(cornerRadius: 18)).shadow(color: Color(hex: "#7C6EFA", opacity: 0.4), radius: 20) }.padding(.top, 24).padding(.bottom, 32).padding(.horizontal, 20) + } } } } diff --git a/AIStudyApp/AIStudyApp/ContentView.swift b/AIStudyApp/AIStudyApp/ContentView.swift index 999046e..9f976f6 100644 --- a/AIStudyApp/AIStudyApp/ContentView.swift +++ b/AIStudyApp/AIStudyApp/ContentView.swift @@ -1,24 +1,111 @@ -// -// ContentView.swift -// AIStudyApp -// -// Created by DSR on 2026/5/4. -// - import SwiftUI struct ContentView: View { + @State private var selectedTab = "ai" + var body: some View { - VStack { - Image(systemName: "globe") - .imageScale(.large) - .foregroundStyle(.tint) - Text("Hello, world!") + ZStack { + switch selectedTab { + case "ai": NavigationStack { AIHomeView() } + case "library": NavigationStack { LibraryHomeView() } + case "study": NavigationStack { StudyHomeView() } + case "analysis": NavigationStack { AnalysisHomeView() } + case "profile": NavigationStack { ProfileView() } + default: NavigationStack { AIHomeView() } + } + VStack { Spacer(); ZXTabBar(active: $selectedTab) } + .ignoresSafeArea(edges: .bottom) } - .padding() + .ignoresSafeArea(edges: .bottom) + .preferredColorScheme(.dark) } } -#Preview { - ContentView() +// MARK: - AI Input Bar + +struct ZXAIInputBar: View { + @Binding var text: String + let onSend: () -> Void + var body: some View { + HStack(spacing: 10) { + Image(systemName: "sparkles").font(.system(size: 16)).foregroundColor(Color.zxPurple) + TextField("问 AI 任何学习问题…", text: $text).font(.system(size: 14)).tint(Color.zxPurple) + Spacer() + Image(systemName: "mic.fill").font(.system(size: 18)).foregroundColor(Color.zxF03) + Button(action: onSend) { + Image(systemName: "arrow.up").font(.system(size: 14, weight: .bold)).foregroundColor(.white) + .frame(width: 30, height: 30).background(ZXGradient.brand).clipShape(RoundedRectangle(cornerRadius: 9)) + } + } + .padding(.horizontal, 14).padding(.vertical, 10) + .background(.ultraThinMaterial).background(Color.zxFill004) + .overlay(RoundedRectangle(cornerRadius: 20).stroke(Color.zxBorder008, lineWidth: 1)) + .clipShape(RoundedRectangle(cornerRadius: 20)) + .padding(.horizontal, 20).padding(.bottom, 34) + } +} + +// MARK: - Score Box + +struct ZXScoreBox: View { + let score: Int; let bg: Color; let fg: Color + var body: some View { + Text("\(score)").font(.system(size: 12, weight: .heavy)).foregroundColor(fg) + .frame(width: 36, height: 36).background(bg).clipShape(RoundedRectangle(cornerRadius: 10)) + } +} + +struct ZXTabBar: View { + @Binding var active: String + private let tabs = [ + ("ai","AI","brain.head.profile"), + ("library","知识库","books.vertical.fill"), + ("study","学习","bolt.fill"), + ("analysis","分析","chart.bar.fill"), + ("profile","我的","person.fill"), + ] + var body: some View { + HStack(spacing: 0) { + ForEach(tabs, id: \.0) { item in + let on = item.0 == active + Button { active = item.0 } label: { + VStack(spacing: 4) { + ZStack { + if on { + Circle().fill(Color.zxPurple.opacity(0.2)) + .frame(width: 28, height: 28).scaleEffect(1.4) + } + Image(systemName: item.2) + .font(.system(size: 22, weight: on ? .semibold : .regular)) + .foregroundColor(on ? Color.zxPurple : Color(hex: "#F0F0FF", opacity: 0.35)) + } + Text(item.1) + .font(.system(size: 10, weight: on ? .semibold : .regular)) + .foregroundColor(on ? Color.zxPurple : Color(hex: "#F0F0FF", opacity: 0.35)) + } + } + .frame(maxWidth: .infinity) + } + } + .padding(.top, 6).padding(.bottom, 34).frame(height: 83) + .background(.ultraThinMaterial).background(Color.zxBg0.opacity(0.95)) + .overlay(alignment: .top) { + Rectangle().fill(Color.zxBorder008).frame(height: 1) + } + } +} + +// MARK: - Shared Icon Button + +struct ZXIconBtn: View { + let icon: String; let size: CGFloat; var branded = false; let action: () -> Void + var body: some View { + Button(action: action) { + Image(systemName: icon).font(.system(size: size * 0.44)).frame(width: size, height: size) + } + .foregroundColor(branded ? .white : Color.zxF05) + .background(branded ? AnyView(ZXGradient.brand) : AnyView(Color(hex: "#FFFFFF", opacity: 0.05))) + .clipShape(RoundedRectangle(cornerRadius: 10)) + .overlay { if !branded { RoundedRectangle(cornerRadius: 10).stroke(Color.zxBorder008, lineWidth: 1) } } + } } diff --git a/AIStudyApp/AIStudyApp/Core/DesignSystem/DesignTokens.swift b/AIStudyApp/AIStudyApp/Core/DesignSystem/DesignTokens.swift new file mode 100644 index 0000000..1a3cea1 --- /dev/null +++ b/AIStudyApp/AIStudyApp/Core/DesignSystem/DesignTokens.swift @@ -0,0 +1,261 @@ +// +// DesignTokens.swift +// AIStudyApp +// +// 1:1 像素级还原 React 原型的设计令牌 +// + +import SwiftUI + +// MARK: - Hex Color Extension + +extension Color { + init(hex: String, opacity: Double = 1.0) { + let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) + var int: UInt64 = 0 + Scanner(string: hex).scanHexInt64(&int) + let r, g, b: UInt64 + switch hex.count { + case 6: + (r, g, b) = ((int >> 16) & 0xFF, (int >> 8) & 0xFF, int & 0xFF) + case 8: + (r, g, b) = ((int >> 24) & 0xFF, (int >> 16) & 0xFF, (int >> 8) & 0xFF) + default: + (r, g, b) = (0, 0, 0) + } + self.init(.sRGB, red: Double(r) / 255, green: Double(g) / 255, blue: Double(b) / 255, opacity: opacity) + } +} + +// MARK: - ZhiXi Colors (exact match from React index.css) + +extension Color { + // ── 背景 ── + static let zxBg0 = Color(hex: "#0F0F1A") + static let zxBg1 = Color(hex: "#12122A") + static let zxBg2 = Color(hex: "#0A0A14") // phone shell + static let zxBgSplash = Color(hex: "#0D0D20") + + // ── 文字 ── + static let zxF0 = Color(hex: "#F0F0FF") + static let zxF05 = Color(hex: "#F0F0FF", opacity: 0.5) + static let zxF04 = Color(hex: "#F0F0FF", opacity: 0.4) + static let zxF03 = Color(hex: "#F0F0FF", opacity: 0.3) + static let zxF007 = Color(hex: "#F0F0FF", opacity: 0.7) + static let zxF006 = Color(hex: "#F0F0FF", opacity: 0.6) + static let zxF0045 = Color(hex: "#F0F0FF", opacity: 0.45) + + // ── 品牌色 ── + static let zxPurple = Color(hex: "#7C6EFA") + static let zxOrange = Color(hex: "#F97316") + static let zxAccent = Color(hex: "#A78BFA") + static let zxTeal = Color(hex: "#2DD4BF") + static let zxGreen = Color(hex: "#34D399") + static let zxYellow = Color(hex: "#F59E0B") + static let zxRed = Color(hex: "#EF4444") + static let zxCyan = Color(hex: "#4ECDC4") + + static let zxF02 = Color(hex: "#F0F0FF", opacity: 0.2) + static let zxFill01 = Color(hex: "#FFFFFF", opacity: 0.1) + + // ── 边框/分割线 ── + static let zxBorder008 = Color(hex: "#FFFFFF", opacity: 0.08) + static let zxBorder006 = Color(hex: "#FFFFFF", opacity: 0.06) + static let zxBorder004 = Color(hex: "#FFFFFF", opacity: 0.04) + static let zxBorder01 = Color(hex: "#FFFFFF", opacity: 0.10) + static let zxBorder015 = Color(hex: "#FFFFFF", opacity: 0.15) + + // ── 半透明填充 ── + static let zxFill003 = Color(hex: "#FFFFFF", opacity: 0.03) + static let zxFill004 = Color(hex: "#FFFFFF", opacity: 0.04) + static let zxFill005 = Color(hex: "#FFFFFF", opacity: 0.05) + static let zxFill006 = Color(hex: "#FFFFFF", opacity: 0.06) + static let zxFill008 = Color(hex: "#FFFFFF", opacity: 0.08) + + // ── 彩色半透 ── + static func zxPurpleBG(_ a: Double = 0.10) -> Color { Color(hex: "#7C6EFA", opacity: a) } + static func zxOrangeBG(_ a: Double = 0.10) -> Color { Color(hex: "#F97316", opacity: a) } + static func zxGreenBG(_ a: Double = 0.10) -> Color { Color(hex: "#34D399", opacity: a) } + static func zxYellowBG(_ a: Double = 0.10) -> Color { Color(hex: "#F59E0B", opacity: a) } + static func zxTealBG(_ a: Double = 0.10) -> Color { Color(hex: "#4ECDC4", opacity: a) } + static func zxRedBG(_ a: Double = 0.15) -> Color { Color(hex: "#EF4444", opacity: a) } +} + +// MARK: - ZhiXi Gradients (exact match) + +enum ZXGradient { + // ── 页面背景 ── + static let page = LinearGradient( + colors: [Color(hex: "#0F0F1A"), Color(hex: "#12122A")], + startPoint: .top, endPoint: .bottom + ) + + // ── 品牌渐变 (135deg) ── + static let brand = LinearGradient( + colors: [Color(hex: "#7C6EFA"), Color(hex: "#F97316")], + startPoint: .topLeading, endPoint: .bottomTrailing + ) + + static let brandPurple = LinearGradient( + colors: [Color(hex: "#7C6EFA"), Color(hex: "#9B8BFF")], + startPoint: .leading, endPoint: .trailing + ) + + // ── 思考卡片 ── + static let thinkingCard = LinearGradient( + colors: [ + Color(hex: "#7C6EFA", opacity: 0.08), + Color(hex: "#F97316", opacity: 0.04) + ], + startPoint: .topLeading, endPoint: .bottomTrailing + ) + + // ── 进度卡片 ── + static let progressCard = LinearGradient( + colors: [ + Color(hex: "#7C6EFA", opacity: 0.10), + Color(hex: "#F97316", opacity: 0.05) + ], + startPoint: .topLeading, endPoint: .bottomTrailing + ) + + // ── 反馈评分卡片 ── + static let feedbackScore = LinearGradient( + colors: [ + Color(hex: "#7C6EFA", opacity: 0.12), + Color(hex: "#34D399", opacity: 0.06) + ], + startPoint: .topLeading, endPoint: .bottomTrailing + ) + + // ── Profile 卡片 ── + static let profileCard = LinearGradient( + colors: [ + Color(hex: "#7C6EFA", opacity: 0.15), + Color(hex: "#F97316", opacity: 0.08) + ], + startPoint: .topLeading, endPoint: .bottomTrailing + ) + + // ── Splash ── + static let splash = LinearGradient( + colors: [ + Color(hex: "#0D0D20"), + Color(hex: "#0F0F1A"), + Color(hex: "#130D20") + ], + startPoint: .top, endPoint: .bottom + ) + + // ── 进度条 ── + static let progressBar = LinearGradient( + colors: [Color(hex: "#7C6EFA"), Color(hex: "#4ECDC4")], + startPoint: .leading, endPoint: .trailing + ) + + // ── CTA 按钮 ── + static let ctaButton = LinearGradient( + colors: [Color(hex: "#7C6EFA"), Color(hex: "#F97316")], + startPoint: .topLeading, endPoint: .bottomTrailing + ) + + static let ctaPurple = LinearGradient( + colors: [Color(hex: "#7C6EFA"), Color(hex: "#9B8BFF")], + startPoint: .topLeading, endPoint: .bottomTrailing + ) +} + +// MARK: - ZhiXi Radii + +enum ZXRadius { + static let xs: CGFloat = 2 + static let sm: CGFloat = 8 + static let md: CGFloat = 10 + static let lg: CGFloat = 12 + static let xl: CGFloat = 14 + static let xl2: CGFloat = 16 + static let xl3: CGFloat = 20 + static let button: CGFloat = 12 + static let buttonLg: CGFloat = 18 + static let icon: CGFloat = 10 + static let iconLg: CGFloat = 12 + static let avatar: CGFloat = 13 +} + +// MARK: - ZhiXi Spacing + +enum ZXSpacing { + static let ss: CGFloat = 2 + static let xs: CGFloat = 4 + static let sm: CGFloat = 6 + static let md: CGFloat = 8 + static let lg: CGFloat = 10 + static let xl: CGFloat = 12 + static let xl2: CGFloat = 14 + static let xl3: CGFloat = 16 + static let xl4: CGFloat = 20 + static let xl5: CGFloat = 24 + static let xl6: CGFloat = 28 + + static let pageHPadding: CGFloat = 20 + static let statusBarH: CGFloat = 44 + static let tabBarH: CGFloat = 83 + static let homeIndicatorH: CGFloat = 34 +} + +// MARK: - ZhiXi Sizing + +enum ZXSize { + static let iconBtn: CGFloat = 36 + static let iconSm: CGFloat = 14 + static let iconMd: CGFloat = 16 + static let iconLg: CGFloat = 18 + static let tabIcon: CGFloat = 22 + static let listIcon: CGFloat = 40 + static let libraryIcon: CGFloat = 44 + static let quickActionH: CGFloat = 72 + static let inputH: CGFloat = 44 + static let buttonH: CGFloat = 42 + static let buttonLgH: CGFloat = 52 + static let buttonXlH: CGFloat = 56 + static let progressH: CGFloat = 5 + static let searchIconBtn: CGFloat = 36 + static let avatarSm: CGFloat = 36 + static let avatarMd: CGFloat = 64 + static let avatarLg: CGFloat = 80 + static let sendBtn: CGFloat = 30 + static let scoreBox: CGFloat = 36 + static let weakBox: CGFloat = 40 + static let topBar: CGFloat = 3 +} + +// MARK: - ZhiXi Typography (exact match from React) + +enum ZXFont { + // titleLarge: 22, heavy, -0.5 + static let titleLarge = (size: CGFloat(22), weight: Font.Weight.heavy, spacing: CGFloat(-0.5)) + // titleMedium: 20, heavy, -0.4 + static let titleMedium = (size: CGFloat(20), weight: Font.Weight.heavy, spacing: CGFloat(-0.4)) + // sectionTitle: 15, bold + static let sectionTitle = (size: CGFloat(15), weight: Font.Weight.bold) + // sectionTitle14: 14, bold + static let subsectionTitle = (size: CGFloat(14), weight: Font.Weight.bold) + // body: 13, semibold, 1.4 + static let body = (size: CGFloat(13), weight: Font.Weight.semibold, lineHeight: CGFloat(1.4)) + // bodySmall: 12, medium + static let bodySmall = (size: CGFloat(12), weight: Font.Weight.medium) + // caption: 10, bold + static let caption = (size: CGFloat(10), weight: Font.Weight.bold) + // captionSmall: 10, regular + static let captionSmall = (size: CGFloat(10), weight: Font.Weight.regular) + // labelXs: 9 + static let labelXs = (size: CGFloat(9), weight: Font.Weight.regular) + // score: 12, heavy + static let score = (size: CGFloat(12), weight: Font.Weight.heavy) + // scoreLg: 22, heavy, 1 + static let scoreLarge = (size: CGFloat(22), weight: Font.Weight.heavy, lineHeight: CGFloat(1)) + // date: 12, medium + static let date = (size: CGFloat(12), weight: Font.Weight.medium) + // description: 12, regular, 0.4 + static let description = (size: CGFloat(12), weight: Font.Weight.regular, spacing: CGFloat(0.4)) +} diff --git a/AIStudyApp/AIStudyApp/Features/AI/AIHomeView.swift b/AIStudyApp/AIStudyApp/Features/AI/AIHomeView.swift new file mode 100644 index 0000000..cb0570d --- /dev/null +++ b/AIStudyApp/AIStudyApp/Features/AI/AIHomeView.swift @@ -0,0 +1,145 @@ +// +// AIHomeView.swift - Page 6: AI Home +// + +import SwiftUI + +struct AIHomeView: View { + @State private var text = "" + + var body: some View { + ZStack { + ZXGradient.page.ignoresSafeArea() + Circle().fill(RadialGradient(colors:[Color(hex:"#7C6EFA",opacity:0.1),.clear],center:.top,startRadius:0,endRadius:200)) + .frame(width:200,height:200).offset(x:80,y:-80).allowsHitTesting(false) + + VStack(spacing:0){ + // Header + HStack(alignment:.bottom){ + VStack(alignment:.leading,spacing:2){ + Text("今天").font(.system(size:12,weight:.medium)).foregroundColor(Color.zxF04) + Text("AI 学习助手").font(.system(size:20,weight:.heavy)).foregroundColor(Color.zxF0).tracking(-0.4) + } + Spacer() + ZXIconBtn(icon:"arrow.clockwise",size:36){} + } + .padding(.horizontal,20).padding(.top,ZXSpacing.statusBarH+16).padding(.bottom,12) + + ScrollView { + VStack(spacing:16){ + thinkingCard + quickActions + recentSection + suggestionSection + } + .padding(.horizontal,20).padding(.bottom,100) + } + .scrollIndicators(.hidden) + + inputBar + } + } + } + + private var thinkingCard: some View { + VStack(alignment:.leading,spacing:12){ + HStack{ + Image(systemName:"sparkles").font(.system(size:14)).foregroundColor(.white) + .frame(width:28,height:28).background(ZXGradient.brandPurple).clipShape(RoundedRectangle(cornerRadius:8)) + Text("今日思考题").font(.system(size:12,weight:.bold)).foregroundColor(Color.zxAccent).tracking(0.5) + Spacer() + Text("待回答").font(.system(size:10,weight:.bold)).foregroundColor(Color(hex:"#FBA574")) + .padding(.horizontal,8).padding(.vertical,2).background(Color(hex:"#F97316",opacity:0.2)).clipShape(Capsule()) + } + Text("解释\"注意力机制\"在 Transformer 中的作用,不能使用搜索,用你自己的话说。") + .font(.system(size:14,weight:.medium)).foregroundColor(Color.zxF0).lineSpacing(4) + NavigationLink(destination: DailyThinkingPage()) { + Text("开始回答").font(.system(size:13,weight:.bold)).foregroundColor(.white) + .frame(maxWidth:.infinity).frame(height:42) + .background(ZXGradient.brand).clipShape(RoundedRectangle(cornerRadius:12)) + } + } + .padding(16).background(ZXGradient.thinkingCard) + .overlay(RoundedRectangle(cornerRadius:20).stroke(Color(hex:"#7C6EFA",opacity:0.1),lineWidth:1)) + .clipShape(RoundedRectangle(cornerRadius:20)) + } + + private var quickActions: some View { + HStack(spacing:12){ + ZXQuickAction(emoji:"🧠",label:"生成\n回忆测试") + ZXQuickAction(emoji:"🔍",label:"分析\n薄弱点") + ZXQuickAction(emoji:"🎤",label:"费曼\n解释练习") + ZXQuickAction(emoji:"📅",label:"今日\n复习计划") + } + } + + private var recentSection: some View { + VStack(alignment:.leading,spacing:12){ + HStack{ + Text("最近 AI 互动").font(.system(size:14,weight:.bold)).foregroundColor(Color.zxF0) + Spacer();Text("全部").font(.system(size:12)).foregroundColor(Color.zxPurple) + } + ZXAIInteractionRow(tag:"费曼复习",bg:Color(hex:"#7C6EFA",opacity:0.15),fg:Color.zxPurple,emoji:"🎤", + title:"解释量子纠缠的核心概念",time:"2小时前",score:82){} + ZXAIInteractionRow(tag:"薄弱点",bg:Color(hex:"#F97316",opacity:0.15),fg:Color(hex:"#FBA574"),emoji:"⚠️", + title:"混淆了协方差和相关系数",time:"昨天",score:56){} + ZXAIInteractionRow(tag:"回忆测试",bg:Color(hex:"#7C6EFA",opacity:0.15),fg:Color.zxAccent,emoji:"📝", + title:"机器学习中的偏差-方差权衡",time:"2天前",score:91){} + } + } + + private var suggestionSection: some View { + VStack(alignment:.leading,spacing:10){ + Text("💡 你可以问 AI").font(.system(size:12,weight:.semibold)).foregroundColor(Color.zxF04) + ForEach(["\"帮我测试机器学习这章的掌握情况\"","\"我最近的薄弱知识点有哪些?\"","\"生成一份本周的复习计划\""],id:\.self){s in + HStack{ + Text(s).font(.system(size:12)).foregroundColor(Color(hex:"#F0F0FF",opacity:0.55)).lineSpacing(4) + Spacer() + Image(systemName:"arrow.up").font(.system(size:12)).foregroundColor(Color(hex:"#7C6EFA",opacity:0.5)) + } + .padding(.horizontal,12).padding(.vertical,8) + .background(Color(hex:"#7C6EFA",opacity:0.06)).clipShape(RoundedRectangle(cornerRadius:10)) + } + } + .padding(14).padding(.horizontal,2) + .background(Color(hex:"#FFFFFF",opacity:0.02)) + .overlay(RoundedRectangle(cornerRadius:16).stroke(Color(hex:"#FFFFFF",opacity:0.04),lineWidth:1)) + .clipShape(RoundedRectangle(cornerRadius:16)) + } + + private var inputBar: some View { + HStack(spacing:10){ + Image(systemName:"sparkles").font(.system(size:16)).foregroundColor(Color.zxPurple) + TextField("问 AI 任何学习问题…",text:$text).font(.system(size:14)).tint(Color.zxPurple) + Spacer() + Image(systemName:"mic.fill").font(.system(size:18)).foregroundColor(Color.zxF03) + Button{}label:{ + Image(systemName:"arrow.up").font(.system(size:14,weight:.bold)).foregroundColor(.white) + .frame(width:30,height:30).background(ZXGradient.brand).clipShape(RoundedRectangle(cornerRadius:9)) + } + } + .padding(.horizontal,14).padding(.vertical,10) + .background(.ultraThinMaterial).background(Color.zxFill004) + .overlay(RoundedRectangle(cornerRadius:20).stroke(Color.zxBorder008,lineWidth:1)) + .clipShape(RoundedRectangle(cornerRadius:20)) + .padding(.horizontal,20) + .padding(.bottom,ZXSpacing.tabBarH+20) + } +} + +// ── Shared UI pieces ── + +struct ZXQuickAction: View {let emoji:String;let label:String + var body: some View {VStack(spacing:6){Text(emoji).font(.system(size:20)) + Text(label).font(.system(size:10,weight:.semibold)).foregroundColor(Color.zxF006).multilineTextAlignment(.center).lineSpacing(4)} + .frame(maxWidth:.infinity).frame(height:72).background(Color.zxFill003) + .overlay(RoundedRectangle(cornerRadius:16).stroke(Color.zxBorder006,lineWidth:1)).clipShape(RoundedRectangle(cornerRadius:16))}} + +struct ZXAIInteractionRow: View {let tag:String;let bg:Color;let fg:Color;let emoji:String;let title:String;let time:String;let score:Int;let action:()->Void + var body: some View {Button(action:action){HStack(spacing:12){ + Text(emoji).font(.system(size:18)).frame(width:40,height:40).background(bg).clipShape(RoundedRectangle(cornerRadius:12)) + VStack(alignment:.leading,spacing:2){HStack(spacing:8){Text(tag).font(.system(size:10,weight:.bold)).foregroundColor(fg).tracking(0.3);Text(time).font(.system(size:10)).foregroundColor(Color.zxF03)};Text(title).font(.system(size:13,weight:.semibold)).foregroundColor(Color(hex:"#F0F0FF",opacity:0.8)).lineLimit(1)} + Spacer() + ZXScoreBox(score:score,bg:score>=80 ? Color.zxGreenBG(0.15) : score>=60 ? Color.zxOrangeBG(0.15):Color.zxRedBG(0.15),fg:score>=80 ? Color.zxGreen : score>=60 ? Color.zxOrange:Color.zxRed)} + .padding(.horizontal,14).padding(.vertical,12).background(Color.zxFill003).overlay(RoundedRectangle(cornerRadius:16).stroke(Color.zxBorder006,lineWidth:1)).clipShape(RoundedRectangle(cornerRadius:16))}} +} diff --git a/AIStudyApp/AIStudyApp/Features/AI/DailyThinkingPage.swift b/AIStudyApp/AIStudyApp/Features/AI/DailyThinkingPage.swift new file mode 100644 index 0000000..0dc5d26 --- /dev/null +++ b/AIStudyApp/AIStudyApp/Features/AI/DailyThinkingPage.swift @@ -0,0 +1,196 @@ +// +// DailyThinkingPage.swift - Page 14: Daily Thinking +// + +import SwiftUI + +struct DailyThinkingPage: View { + @State private var answer = "" + @State private var submitted = false + + var body: some View { + ZStack { Color.zxBg0.ignoresSafeArea() + VStack(spacing: 0) { + ZXBackHeader(title: "今日思考", subtitle: "过拟合", onBack: nil, trailing: { + ZXIconBtn(icon: "bookmark", size: 36) {} + }) + ScrollView { VStack(spacing: 16) { + VStack(alignment: .leading, spacing: 12) { + HStack { + Image(systemName: "sparkles").foregroundColor(Color.zxAccent) + Text("解释\"注意力机制\"在 Transformer 中的作用").font(.system(size: 15, weight: .bold)).foregroundColor(Color.zxF0) + } + Text("AI会从三个方面评估你的回答:核心概念理解 · 理论深度 · 实际应用能力").font(.system(size: 12)).foregroundColor(Color.zxF04) + } + .padding(16).background(ZXGradient.thinkingCard).clipShape(RoundedRectangle(cornerRadius: 16)) + + VStack(alignment: .leading, spacing: 8) { + Text("你的回答").font(.system(size: 13, weight: .semibold)).foregroundColor(Color.zxF04) + TextEditor(text: $answer) + .font(.system(size: 13)).foregroundColor(Color.zxF0).tint(Color.zxPurple) + .frame(minHeight: 160).scrollContentBackground(.hidden) + .padding(12).background(Color.zxFill004).clipShape(RoundedRectangle(cornerRadius: 14)).overlay(RoundedRectangle(cornerRadius: 14).stroke(Color.zxBorder008, lineWidth: 1)) + } + + if !submitted { + NavigationLink(destination: AIFeedbackPageView()) { + Text("提交回答,获取 AI 反馈").font(.system(size: 14, weight: .bold)).foregroundColor(.white) + .frame(maxWidth: .infinity).frame(height: 52) + .background(ZXGradient.ctaPurple).clipShape(RoundedRectangle(cornerRadius: 16)) + .shadow(color: Color(hex: "#7C6EFA", opacity: 0.3), radius: 24) + } + } + }.padding(.horizontal, 20).padding(.bottom, 120) } + .scrollIndicators(.hidden) + } + } + .navigationBarHidden(true) + } +} + +// Back Header +struct ZXBackHeader: View { + let title: String; let subtitle: String?; var onBack: (() -> Void)? + @ViewBuilder var trailing: () -> T + @Environment(\.dismiss) private var dismiss + var body: some View { + HStack { + Button { (onBack ?? { dismiss() })() } label: { + Image(systemName: "chevron.left").font(.system(size: 18)).foregroundColor(Color.zxF007) + .frame(width: 36, height: 36).background(Color.zxFill005).clipShape(RoundedRectangle(cornerRadius: 10)) + .overlay(RoundedRectangle(cornerRadius: 10).stroke(Color.zxBorder008, lineWidth: 1)) + } + VStack(spacing: 1) { + Text(title).font(.system(size: 15, weight: .bold)).foregroundColor(Color.zxF0) + if let s = subtitle { Text(s).font(.system(size: 11)).foregroundColor(Color.zxF03) } + }.frame(maxWidth: .infinity) + trailing() + } + .padding(.horizontal, 16).padding(.top, ZXSpacing.statusBarH + 8).padding(.bottom, 12) + .background(Color.zxBg0) + } +} + +// RecallTest +struct RecallTestPage: View { + @State private var input = "" + var body: some View { + ZStack { Color.zxBg0.ignoresSafeArea() + VStack(spacing: 0) { + ZXBackHeader(title: "回忆测试", subtitle: "机器学习 · 偏差-方差权衡") {} + ScrollView { VStack(spacing: 16) { + Text("请回忆并写下你对「偏差-方差权衡」的理解").font(.system(size: 14)).foregroundColor(Color.zxF04) + TextEditor(text: $input).frame(minHeight: 200).scrollContentBackground(.hidden).padding(12).background(Color.zxFill004).clipShape(RoundedRectangle(cornerRadius: 14)).overlay(RoundedRectangle(cornerRadius: 14).stroke(Color.zxBorder008, lineWidth: 1)) + Button {} label: { Text("提交").font(.system(size: 14, weight: .bold)).foregroundColor(.white).frame(maxWidth: .infinity).frame(height: 52).background(ZXGradient.ctaPurple).clipShape(RoundedRectangle(cornerRadius: 16)) } + }.padding(.horizontal, 20).padding(.bottom, 80) }.scrollIndicators(.hidden) + } + }.navigationBarHidden(true) + } +} + +// WeakPoints +struct WeakPointsPage: View { + var body: some View { + ZStack { Color.zxBg0.ignoresSafeArea() + VStack(spacing: 0) { + ZXBackHeader(title: "薄弱知识点", subtitle: "共 23 个待巩固") {} + ScrollView { VStack(spacing: 12) { + ZXWeakRow(score: 32, topic: "贝叶斯定理应用", lib: "机器学习", priority: "高") + ZXWeakRow(score: 41, topic: "正态分布性质", lib: "高等数学", priority: "高") + ZXWeakRow(score: 55, topic: "词根 spect- 相关词汇", lib: "英语词汇", priority: "中") + ZXWeakRow(score: 48, topic: "协方差与相关系数", lib: "机器学习", priority: "中") + ZXWeakRow(score: 36, topic: "梯度下降优化", lib: "机器学习", priority: "高") + }.padding(.horizontal, 20).padding(.bottom, 80) }.scrollIndicators(.hidden) + } + }.navigationBarHidden(true) + } +} + +// AIFeedback - Page 15 +struct AIFeedbackPageView: View { + var body: some View { + ZStack { Color.zxBg0.ignoresSafeArea() + VStack(spacing: 0) { + ZXBackHeader(title: "AI 反馈", subtitle: "今日思考 · 过拟合", trailing: { + ZXIconBtn(icon: "bookmark", size: 36) {} + }) + ScrollView { VStack(spacing: 16) { + // Score + HStack(spacing: 20) { + ZStack { + Circle().trim(from: 0, to: 0.78).stroke(ZXGradient.brand, style: StrokeStyle(lineWidth: 10, lineCap: .round)).rotationEffect(.degrees(-90)).frame(width: 80, height: 80) + VStack(spacing: 0) { Text("78").font(.system(size: 22, weight: .heavy)).foregroundColor(Color.zxPurple); Text("/ 100").font(.system(size: 9)).foregroundColor(Color.zxF04) } + } + VStack(alignment: .leading, spacing: 2) { + Text("良好掌握").font(.system(size: 18, weight: .heavy)).foregroundColor(Color.zxF0) + Text("理解核心概念,但缺少理论深度和解决方案").font(.system(size: 12)).foregroundColor(Color.zxF0045).lineSpacing(4) + } + Spacer() + } + .padding(20).background(ZXGradient.feedbackScore).clipShape(RoundedRectangle(cornerRadius: 20)).overlay(RoundedRectangle(cornerRadius: 20).stroke(Color(hex: "#7C6EFA", opacity: 0.2), lineWidth: 1)) + + // 你的回答 + VStack(alignment: .leading, spacing: 8) { Text("你的回答").font(.system(size: 13, weight: .semibold)).foregroundColor(Color.zxF04); Text("过拟合就像一个学生只会「死记硬背」考题,而不是真正理解知识…").font(.system(size: 13)).foregroundColor(Color.zxF007).lineSpacing(6).padding(14).background(Color.zxFill004).clipShape(RoundedRectangle(cornerRadius: 14)).overlay(RoundedRectangle(cornerRadius: 14).stroke(Color.zxBorder006, lineWidth: 1)) } + + // 答对的部分 + VStack(alignment: .leading, spacing: 8) { + HStack(spacing: 8) { Image(systemName: "checkmark.circle.fill").foregroundColor(Color.zxGreen); Text("答对的部分").font(.system(size: 14, weight: .bold)).foregroundColor(Color.zxF0) } + ForEach([("正确识别出过拟合是\"记住训练数据\"而非\"学习规律\""),("使用了死记硬背类比,方向正确且贴切")], id: \.self) { s in + HStack(alignment: .top, spacing: 12) { Circle().fill(Color.zxGreen).frame(width: 6, height: 6).padding(.top, 6); Text(s).font(.system(size: 13)).foregroundColor(Color(hex: "#F0F0FF", opacity: 0.75)).lineSpacing(4) } + .padding(12).background(Color(hex: "#34D399", opacity: 0.07)).clipShape(RoundedRectangle(cornerRadius: 12)).overlay(RoundedRectangle(cornerRadius: 12).stroke(Color(hex: "#34D399", opacity: 0.18), lineWidth: 1)) + } + } + + // 需要完善 + VStack(alignment: .leading, spacing: 10) { + HStack(spacing: 8) { Image(systemName: "exclamationmark.triangle.fill").foregroundColor(Color.zxYellow); Text("需要完善").font(.system(size: 14, weight: .bold)).foregroundColor(Color.zxF0) } + ForEach([("缺少对「方差」和「偏差」权衡的说明", "过拟合本质是高方差问题,可以提到偏差-方差权衡"),("未提及正则化、Dropout 等解决方案", "完整答案通常要说明\"如何解决\"")], id: \.0) { p, d in + VStack(alignment: .leading, spacing: 4) { Text(p).font(.system(size: 13, weight: .bold)).foregroundColor(Color.zxF0); Text(d).font(.system(size: 12)).foregroundColor(Color.zxF05).lineSpacing(4) } + .padding(14).frame(maxWidth: .infinity, alignment: .leading).background(Color(hex: "#F59E0B", opacity: 0.07)).clipShape(RoundedRectangle(cornerRadius: 14)).overlay(RoundedRectangle(cornerRadius: 14).stroke(Color(hex: "#F59E0B", opacity: 0.18), lineWidth: 1)) + } + } + + // 参考答案 + VStack(alignment: .leading, spacing: 8) { + Text("✨ 参考答案要点").font(.system(size: 13, weight: .bold)).foregroundColor(Color.zxAccent) + Text("过拟合是模型复杂度过高导致的高方差问题。偏差-方差权衡是核心概念。解决方法包括:正则化、Dropout、数据增强、早停等。").font(.system(size: 13)).foregroundColor(Color.zxF007).lineSpacing(6) + }.padding(16).background(Color(hex: "#7C6EFA", opacity: 0.07)).clipShape(RoundedRectangle(cornerRadius: 16)).overlay(RoundedRectangle(cornerRadius: 16).stroke(Color(hex: "#7C6EFA", opacity: 0.2), lineWidth: 1)) + + // 操作按钮 + Button {} label: { Label("加入待巩固,安排间隔复习", systemImage: "bolt.fill").font(.system(size: 14, weight: .bold)).foregroundColor(.white).frame(maxWidth: .infinity).frame(height: 52).background(ZXGradient.ctaPurple).clipShape(RoundedRectangle(cornerRadius: 16)).shadow(color: Color(hex: "#7C6EFA", opacity: 0.3), radius: 24) } + HStack(spacing: 12) { ZXOutlineBtn(text: "深入提问"); ZXOutlineBtn(text: "再来一题") } + }.padding(.horizontal, 20).padding(.bottom, 80) }.scrollIndicators(.hidden) + } + }.navigationBarHidden(true) + } +} +struct ZXOutlineBtn: View { let text: String + var body: some View { Button {} label: { HStack(spacing: 4) { Text(text).font(.system(size: 13)); Image(systemName: "chevron.right").font(.system(size: 14)) }.foregroundColor(Color.zxF05).frame(maxWidth: .infinity).frame(height: 44).background(Color.zxFill005).clipShape(RoundedRectangle(cornerRadius: 14)).overlay(RoundedRectangle(cornerRadius: 14).stroke(Color.zxBorder008, lineWidth: 1)) } } +} + +// AIChat - Page 11 +struct AIChatPage: View { + @State private var msg = "" + @State private var msgs: [(role: String, content: String)] = [("ai", "你好!我是你的 AI 学习助手。我可以帮你解答学习问题、分析薄弱点、制定复习计划。请告诉我你想学习什么?")] + var body: some View { + ZStack { Color.zxBg0.ignoresSafeArea() + VStack(spacing: 0) { + ZXBackHeader(title: "AI 对话", subtitle: "学习助手") {} + ScrollViewReader { proxy in ScrollView { VStack(spacing: 16) { + ForEach(Array(msgs.enumerated()), id: \.offset) { i, m in + HStack(alignment: .top, spacing: 8) { + if m.role == "ai" { + Image(systemName: "brain.head.profile").foregroundColor(Color.zxPurple).frame(width: 28, height: 28).background(Color(hex: "#7C6EFA", opacity: 0.15)).clipShape(Circle()) + } + Text(m.content).font(.system(size: 14)).foregroundColor(m.role == "user" ? .white : Color.zxF007).padding(12).background(m.role == "user" ? AnyView(ZXGradient.brandPurple) : AnyView(Color.zxFill004)).clipShape(RoundedRectangle(cornerRadius: 16)) + if m.role == "user" { Circle().frame(width: 28, height: 28).foregroundColor(Color.zxPurpleBG(0.2)).overlay(Text("我").font(.system(size: 10, weight: .bold)).foregroundColor(Color.zxPurple)) } + } + .frame(maxWidth: .infinity, alignment: m.role == "user" ? .trailing : .leading) + } + }.padding(.horizontal, 20).padding(.bottom, 100).id("bottom") }.scrollIndicators(.hidden) + .onChange(of: msgs.count) { withAnimation { proxy.scrollTo("bottom") } } } + ZXAIInputBar(text: $msg, onSend: { guard !msg.isEmpty else { return }; msgs.append(("user", msg)); msg = ""; DispatchQueue.main.asyncAfter(deadline: .now() + 1) { msgs.append(("ai", "好的,我理解你的问题。建议你从基础概念开始,逐步深入理解。需要我帮你制定具体的学习计划吗?")) } }) + } + }.navigationBarHidden(true) + } +} diff --git a/AIStudyApp/AIStudyApp/Features/Analysis/AnalysisHomeView.swift b/AIStudyApp/AIStudyApp/Features/Analysis/AnalysisHomeView.swift new file mode 100644 index 0000000..bbaee8f --- /dev/null +++ b/AIStudyApp/AIStudyApp/Features/Analysis/AnalysisHomeView.swift @@ -0,0 +1,148 @@ +// +// AnalysisHomeView.swift - Page 9: Analysis Home +// + +import SwiftUI + +struct AnalysisHomeView: View { + var body: some View { + ZStack { + Color.zxBg0.ignoresSafeArea() + VStack(spacing: 0) { + HStack { + Text("学习分析") + .font(.system(size: 22, weight: .heavy)).foregroundColor(Color.zxF0).tracking(-0.5) + Spacer() + HStack(spacing: 4) { + Text("近 7 天").font(.system(size: 12)).foregroundColor(Color.zxF05) + Image(systemName: "chevron.down").font(.system(size: 10)).foregroundColor(Color.zxF04) + } + .padding(.horizontal, 12).padding(.vertical, 6) + .background(Color.zxFill005) + .clipShape(Capsule()) + .overlay(Capsule().stroke(Color.zxBorder008, lineWidth: 1)) + } + .padding(.horizontal, 20).padding(.top, ZXSpacing.statusBarH + 16).padding(.bottom, 12) + + ScrollView { + VStack(spacing: 16) { + HStack(spacing: 12) { + ZXStatBadge(icon: "trophy.fill", label: "综合掌握", value: "65%", trend: "+8%", color: Color.zxPurple) + ZXStatBadge(icon: "bolt.fill", label: "本周积分", value: "1,240", trend: "+320", color: Color.zxOrange) + ZXStatBadge(icon: "exclamationmark.triangle.fill", label: "待巩固", value: "23", trend: "-5", color: Color.zxYellow) + ZXStatBadge(icon: "chart.line.uptrend.xyaxis", label: "连续天", value: "14", trend: "🔥", color: Color.zxGreen) + } + + VStack(alignment: .leading, spacing: 16) { + HStack { + Text("掌握度趋势").font(.system(size: 14, weight: .bold)).foregroundColor(Color.zxF0) + Spacer() + Text("↑ +8% 本周").font(.system(size: 12, weight: .semibold)).foregroundColor(Color.zxGreen) + } + ZXChartView() + } + .padding(16) + .background(Color.zxFill004) + .overlay(RoundedRectangle(cornerRadius: 20).stroke(Color.zxBorder006, lineWidth: 1)) + .clipShape(RoundedRectangle(cornerRadius: 20)) + + VStack(alignment: .leading, spacing: 12) { + HStack { + HStack(spacing: 8) { + Image(systemName: "exclamationmark.triangle.fill").font(.system(size: 14)).foregroundColor(Color.zxYellow) + Text("薄弱知识点").font(.system(size: 15, weight: .bold)).foregroundColor(Color.zxF0) + } + Spacer() + Text("全部 23 个").font(.system(size: 12)).foregroundColor(Color.zxPurple) + } + ZXWeakRow(score: 32, topic: "贝叶斯定理应用", lib: "机器学习", priority: "高") + ZXWeakRow(score: 41, topic: "正态分布性质", lib: "高等数学", priority: "高") + ZXWeakRow(score: 55, topic: "词根 spect- 相关词汇", lib: "英语词汇", priority: "中") + } + } + .padding(.horizontal, 20) + .padding(.bottom, 120) + } + .scrollIndicators(.hidden) + } + } + } +} + +// MARK: - Stat Badge + +struct ZXStatBadge: View { + let icon: String; let label: String; let value: String; let trend: String; let color: Color + var body: some View { + VStack(spacing: 3) { + Image(systemName: icon).font(.system(size: 14)).foregroundColor(color) + Text(value).font(.system(size: 16, weight: .heavy)).foregroundColor(Color.zxF0) + Text(label).font(.system(size: 9)).foregroundColor(Color.zxF04).multilineTextAlignment(.center) + } + .frame(maxWidth: .infinity).frame(height: 72).padding(.vertical, 4) + .background(color.opacity(0.06)) + .overlay(RoundedRectangle(cornerRadius: 14).stroke(color.opacity(0.15), lineWidth: 1)) + .clipShape(RoundedRectangle(cornerRadius: 14)) + } +} + +// MARK: - Weak Point Row + +struct ZXWeakRow: View { + let score: Int; let topic: String; let lib: String; let priority: String + var body: some View { + HStack(spacing: 12) { + Text("\(score)").font(.system(size: 13, weight: .heavy)).foregroundColor(Color.zxYellow) + .frame(width: 40, height: 40) + .background(Color.zxYellowBG(0.15)).clipShape(RoundedRectangle(cornerRadius: 12)) + VStack(alignment: .leading, spacing: 2) { + Text(topic).font(.system(size: 13, weight: .semibold)).foregroundColor(Color.zxF0) + Text(lib).font(.system(size: 11)).foregroundColor(Color.zxF04) + }.frame(maxWidth: .infinity, alignment: .leading) + Text("\(priority)优先").font(.system(size: 11, weight: .bold)) + .foregroundColor(priority == "高" ? Color.zxRed : Color.zxYellow) + .padding(.horizontal, 8).padding(.vertical, 3) + .background((priority == "高" ? Color.zxRedBG(0.15) : Color.zxYellowBG(0.15))) + .clipShape(Capsule()) + } + .padding(.horizontal, 16).padding(.vertical, 12) + .background(Color.zxYellowBG(0.06)) + .overlay(RoundedRectangle(cornerRadius: 14).stroke(Color(hex: "#F59E0B", opacity: 0.15), lineWidth: 1)) + .clipShape(RoundedRectangle(cornerRadius: 14)) + } +} + +// MARK: - Chart View + +struct ZXChartView: View { + let data: [(String, CGFloat)] = [ + ("一", 0.62), ("二", 0.65), ("三", 0.71), ("四", 0.68), + ("五", 0.75), ("六", 0.79), ("今", 0.78) + ] + var body: some View { + VStack(spacing: 0) { + GeometryReader { g in + ZStack(alignment: .topLeading) { + Path { path in + let w = g.size.width / 7 + for (i, d) in data.enumerated() { + let x = w * CGFloat(i) + w / 2 + let y = (1 - d.1) * g.size.height + if i == 0 { path.move(to: CGPoint(x: x, y: y)) } + else { path.addLine(to: CGPoint(x: x, y: y)) } + } + } + .stroke(Color.zxPurple, style: StrokeStyle(lineWidth: 2, lineCap: .round, lineJoin: .round)) + } + } + .frame(height: 100) + HStack(spacing: 0) { + ForEach(data, id: \.0) { d in + Text(d.0).font(.system(size: 9)) + .foregroundColor(Color(hex: "#F0F0FF", opacity: 0.35)) + .frame(maxWidth: .infinity) + } + } + } + } +} diff --git a/AIStudyApp/AIStudyApp/Features/Library/LibraryHomeView.swift b/AIStudyApp/AIStudyApp/Features/Library/LibraryHomeView.swift new file mode 100644 index 0000000..eda2e1f --- /dev/null +++ b/AIStudyApp/AIStudyApp/Features/Library/LibraryHomeView.swift @@ -0,0 +1,35 @@ +// +// LibraryHomeView.swift - Page 7 +// + +import SwiftUI + +struct LibraryHomeView: View { + @State private var s = "" + var body: some View { + ZStack { ZXGradient.page.ignoresSafeArea() + VStack(spacing: 0) { + HStack { Text("知识库").font(.system(size: 22, weight: .heavy)).foregroundColor(Color.zxF0).tracking(-0.5); Spacer(); ZXIconBtn(icon: "magnifyingglass", size: 36) {}; ZXIconBtn(icon: "plus", size: 36, branded: true) {} } + .padding(.horizontal, 20).padding(.top, ZXSpacing.statusBarH + 16).padding(.bottom, 12) + HStack(spacing: 8) { Image(systemName: "magnifyingglass").font(.system(size: 16)).foregroundColor(Color.zxF03); TextField("搜索知识库或知识点…", text: $s).font(.system(size: 14)).tint(Color.zxPurple) } + .padding(.horizontal, 14).frame(height: 44).background(Color.zxFill004).overlay(RoundedRectangle(cornerRadius: 14).stroke(Color.zxBorder008, lineWidth: 1)).clipShape(RoundedRectangle(cornerRadius: 14)).padding(.horizontal, 20).padding(.bottom, 16) + ScrollView { VStack(spacing: 12) { + NavigationLink(destination: LibraryDetailPage()) { ZLibraryCard(emoji: "🤖", name: "机器学习", desc: "ML基础 · 深度学习 · 实战项目", color: Color.zxPurple, items: 47, mastery: 72, tags: ["算法","数学","实战"], last: "今天") } + NavigationLink(destination: LibraryDetailPage()) { ZLibraryCard(emoji: "📐", name: "高等数学", desc: "微积分 · 线代 · 概率论", color: Color.zxOrange, items: 93, mastery: 58, tags: ["公式","定理","习题"], last: "昨天") } + NavigationLink(destination: LibraryDetailPage()) { ZLibraryCard(emoji: "📖", name: "英语词汇", desc: "GRE · 托福 · 商务英语", color: Color.zxTeal, items: 312, mastery: 84, tags: ["词根","语境","拼写"], last: "3天前") } + NavigationLink(destination: LibraryDetailPage()) { ZLibraryCard(emoji: "🎨", name: "产品设计", desc: "UX 方法论 · 用研 · 交互规范", color: Color.zxYellow, items: 28, mastery: 43, tags: ["方法论","案例"], last: "1周前") } + NavigationLink(destination: CreateLibraryPage()) { + HStack(spacing: 8) { Image(systemName: "plus").font(.system(size: 16)); Text("创建新知识库").font(.system(size: 14, weight: .semibold)) } + .foregroundColor(Color.zxF05).frame(maxWidth: .infinity).frame(height: 52).background(Color.zxFill003) + .overlay(RoundedRectangle(cornerRadius: 16).strokeBorder(style: StrokeStyle(lineWidth: 1.5, dash: [6, 4]), antialiased: true).foregroundColor(Color.zxBorder01)) + .clipShape(RoundedRectangle(cornerRadius: 16)) + } + }.padding(.horizontal, 20).padding(.bottom, 120) }.scrollIndicators(.hidden) + } + } + } +} +struct ZLibraryCard: View { let emoji: String; let name: String; let desc: String; let color: Color; let items: Int; let mastery: Int; let tags: [String]; let last: String + var body: some View { VStack(spacing: 0) { Rectangle().fill(ZXGradient.progressBar).frame(height: 3); HStack(spacing: 12) { Text(emoji).font(.system(size: 22)).frame(width: 44, height: 44).background(color.opacity(0.12)).clipShape(RoundedRectangle(cornerRadius: 13)).overlay(RoundedRectangle(cornerRadius: 13).stroke(color.opacity(0.3), lineWidth: 1)); VStack(alignment: .leading, spacing: 2) { Text(name).font(.system(size: 16, weight: .bold)).foregroundColor(Color.zxF0); Text(desc).font(.system(size: 12)).foregroundColor(Color.zxF04); Text("掌握 \(mastery)%").font(.system(size: 11)).foregroundColor(Color.zxF04) }; Spacer() }.padding(16); HStack { HStack(spacing: 4) { Image(systemName: "clock").font(.system(size: 10)); Text("\(items) 项 · \(last)").font(.system(size: 11)) }.foregroundColor(Color.zxF03); Spacer(); ForEach(tags.prefix(2), id: \.self) { t in Text(t).font(.system(size: 10, weight: .medium)).foregroundColor(Color.zxPurple).padding(.horizontal, 7).padding(.vertical, 2).background(Color(hex: "#7C6EFA", opacity: 0.08)).clipShape(Capsule()) } }.padding(.horizontal, 16).padding(.bottom, 12) } + .background(Color.zxFill003).clipShape(RoundedRectangle(cornerRadius: 20)).overlay(RoundedRectangle(cornerRadius: 20).stroke(Color.zxBorder006, lineWidth: 1)) } +} diff --git a/AIStudyApp/AIStudyApp/Features/Library/LibrarySubpages.swift b/AIStudyApp/AIStudyApp/Features/Library/LibrarySubpages.swift new file mode 100644 index 0000000..70a7bb3 --- /dev/null +++ b/AIStudyApp/AIStudyApp/Features/Library/LibrarySubpages.swift @@ -0,0 +1,111 @@ +// +// LibrarySubpages.swift +// + +import SwiftUI + +struct CreateLibraryPage: View { + @State private var name = ""; @State private var desc = "" + var body: some View { + ZStack { Color.zxBg0.ignoresSafeArea() + VStack(spacing: 0) { + ZXBackHeader(title: "创建知识库", subtitle: nil) {} + ScrollView { VStack(spacing: 20) { + VStack(alignment: .leading, spacing: 8) { Text("知识库名称").font(.system(size: 12, weight: .semibold)).foregroundColor(Color.zxF035); TextField("例如:机器学习", text: $name).font(.system(size: 15)).tint(Color.zxPurple).padding(.horizontal, 16).frame(height: 52).background(Color.zxFill004).clipShape(RoundedRectangle(cornerRadius: 14)).overlay(RoundedRectangle(cornerRadius: 14).stroke(Color.zxBorder008, lineWidth: 1)) } + VStack(alignment: .leading, spacing: 8) { Text("描述(可选)").font(.system(size: 12, weight: .semibold)).foregroundColor(Color.zxF035); TextField("简单描述这个知识库的内容", text: $desc).font(.system(size: 15)).tint(Color.zxPurple).padding(.horizontal, 16).frame(height: 52).background(Color.zxFill004).clipShape(RoundedRectangle(cornerRadius: 14)).overlay(RoundedRectangle(cornerRadius: 14).stroke(Color.zxBorder008, lineWidth: 1)) } + Button {} label: { Text("创建").font(.system(size: 14, weight: .bold)).foregroundColor(.white).frame(maxWidth: .infinity).frame(height: 52).background(ZXGradient.ctaPurple).clipShape(RoundedRectangle(cornerRadius: 16)) } + }.padding(.horizontal, 20).padding(.top, 20) }.scrollIndicators(.hidden) + } + }.navigationBarHidden(true) + } +} + +struct LibraryDetailPage: View { + var body: some View { + ZStack { Color.zxBg0.ignoresSafeArea() + VStack(spacing: 0) { + ZXBackHeader(title: "机器学习", subtitle: "47 个知识点 · 掌握 72%") { + HStack(spacing: 8) { ZXIconBtn(icon: "magnifyingglass", size: 36) {}; ZXIconBtn(icon: "plus", size: 36, branded: true) {} } + } + ScrollView { VStack(spacing: 12) { + NavigationLink(destination: KnowledgeDetailPage()) { ZXCardRow(emoji: "📝", title: "偏差-方差权衡", desc: "模型复杂度 · 泛化误差", status: "已掌握", c: Color.zxGreen) } + NavigationLink(destination: KnowledgeDetailPage()) { ZXCardRow(emoji: "📝", title: "梯度下降优化", desc: "SGD · Adam · 学习率", status: "学习中", c: Color.zxOrange) } + NavigationLink(destination: KnowledgeDetailPage()) { ZXCardRow(emoji: "📝", title: "正则化方法", desc: "L1 · L2 · Dropout", status: "待复习", c: Color.zxYellow) } + NavigationLink(destination: KnowledgeDetailPage()) { ZXCardRow(emoji: "📝", title: "过拟合与欠拟合", desc: "偏差方差 · 模型选择", status: "已掌握", c: Color.zxGreen) } + }.padding(.horizontal, 20).padding(.bottom, 80) }.scrollIndicators(.hidden) + } + }.navigationBarHidden(true) + } +} +struct ZXCardRow: View { let emoji: String; let title: String; let desc: String; let status: String; let c: Color + var body: some View { HStack(spacing: 12) { Text(emoji).font(.system(size: 20)).frame(width: 40, height: 40).background(Color.zxFill004).clipShape(RoundedRectangle(cornerRadius: 10)); VStack(alignment: .leading, spacing: 2) { Text(title).font(.system(size: 14, weight: .semibold)).foregroundColor(Color.zxF0); Text(desc).font(.system(size: 11)).foregroundColor(Color.zxF03) }; Spacer(); Text(status).font(.system(size: 10, weight: .semibold)).foregroundColor(c).padding(.horizontal, 8).padding(.vertical, 2).background(c.opacity(0.12)).clipShape(Capsule()) } + .padding(14).background(Color.zxFill003).clipShape(RoundedRectangle(cornerRadius: 14)).overlay(RoundedRectangle(cornerRadius: 14).stroke(Color.zxBorder006, lineWidth: 1)) } +} + +struct AddKnowledgePage: View { + @State private var title = ""; @State private var content = "" + var body: some View { + ZStack { Color.zxBg0.ignoresSafeArea() + VStack(spacing: 0) { + ZXBackHeader(title: "添加知识点", subtitle: "机器学习") {} + ScrollView { VStack(spacing: 16) { + VStack(alignment: .leading, spacing: 8) { Text("标题").font(.system(size: 12, weight: .semibold)).foregroundColor(Color.zxF035); TextField("输入知识点标题", text: $title).font(.system(size: 15)).tint(Color.zxPurple).padding(.horizontal, 16).frame(height: 52).background(Color.zxFill004).clipShape(RoundedRectangle(cornerRadius: 14)).overlay(RoundedRectangle(cornerRadius: 14).stroke(Color.zxBorder008, lineWidth: 1)) } + VStack(alignment: .leading, spacing: 8) { Text("内容").font(.system(size: 12, weight: .semibold)).foregroundColor(Color.zxF035); TextEditor(text: $content).frame(minHeight: 200).scrollContentBackground(.hidden).padding(12).background(Color.zxFill004).clipShape(RoundedRectangle(cornerRadius: 14)).overlay(RoundedRectangle(cornerRadius: 14).stroke(Color.zxBorder008, lineWidth: 1)) } + Button {} label: { Text("保存").font(.system(size: 14, weight: .bold)).foregroundColor(.white).frame(maxWidth: .infinity).frame(height: 52).background(ZXGradient.ctaPurple).clipShape(RoundedRectangle(cornerRadius: 16)) } + }.padding(.horizontal, 20).padding(.bottom, 80) }.scrollIndicators(.hidden) + } + }.navigationBarHidden(true) + } +} + +struct KnowledgeDetailPage: View { + var body: some View { + ZStack { Color.zxBg0.ignoresSafeArea() + VStack(spacing: 0) { + ZXBackHeader(title: "知识点详情", subtitle: "机器学习") { ZXIconBtn(icon: "pencil", size: 36) {} } + ScrollView { VStack(spacing: 16) { + VStack(alignment: .leading, spacing: 8) { HStack { ZXChip(text: "算法", color: Color.zxPurple); ZXChip(text: "机器学习", color: Color.zxAccent); ZXChip(text: "需要复习", color: Color.zxYellow) }; Text("偏差-方差权衡").font(.system(size: 22, weight: .heavy)).foregroundColor(Color.zxF0); Text("偏差-方差权衡是机器学习模型选择的核心理念。").font(.system(size: 14)).foregroundColor(Color.zxF007).lineSpacing(6) }.padding(20).background(Color.zxFill004).clipShape(RoundedRectangle(cornerRadius: 20)).overlay(RoundedRectangle(cornerRadius: 20).stroke(Color.zxBorder006, lineWidth: 1)) + HStack(spacing: 12) { Button {} label: { Label("开始复习", systemImage: "arrow.triangle.2.circlepath").font(.system(size: 14, weight: .bold)).foregroundColor(.white).frame(maxWidth: .infinity).frame(height: 44).background(ZXGradient.brandPurple).clipShape(RoundedRectangle(cornerRadius: 14)) }; Button {} label: { Label("费曼解释", systemImage: "mic.fill").font(.system(size: 14, weight: .semibold)).foregroundColor(Color.zxF05).frame(maxWidth: .infinity).frame(height: 44).background(Color.zxFill005).clipShape(RoundedRectangle(cornerRadius: 14)).overlay(RoundedRectangle(cornerRadius: 14).stroke(Color.zxBorder008, lineWidth: 1)) } } + }.padding(.horizontal, 20).padding(.bottom, 80) }.scrollIndicators(.hidden) + } + }.navigationBarHidden(true) + } +} +struct ZXChip: View { let text: String; let color: Color + var body: some View { Text(text).font(.system(size: 10, weight: .semibold)).foregroundColor(color).padding(.horizontal, 8).padding(.vertical, 2).background(color.opacity(0.12)).clipShape(Capsule()) } +} + +struct ImportPage: View { + var body: some View { + ZStack { Color.zxBg0.ignoresSafeArea() + VStack(spacing: 0) { + ZXBackHeader(title: "导入资料", subtitle: nil) {} + ScrollView { VStack(spacing: 12) { + ZXImportOption(icon: "camera.fill", title: "拍照导入", desc: "拍下书本或笔记,AI 自动识别") + ZXImportOption(icon: "doc.text.fill", title: "文件导入", desc: "支持 PDF、Word、Markdown") + ZXImportOption(icon: "link", title: "链接导入", desc: "粘贴网页链接,自动提取内容") + ZXImportOption(icon: "photo.on.rectangle", title: "相册导入", desc: "从相册选择截图或图片") + }.padding(.horizontal, 20).padding(.top, 16) }.scrollIndicators(.hidden) + } + }.navigationBarHidden(true) + } +} +struct ZXImportOption: View { let icon: String; let title: String; let desc: String + var body: some View { Button {} label: { HStack(spacing: 14) { Image(systemName: icon).font(.system(size: 22)).foregroundColor(Color.zxPurple).frame(width: 48, height: 48).background(Color.zxPurpleBG(0.1)).clipShape(RoundedRectangle(cornerRadius: 14)); VStack(alignment: .leading, spacing: 2) { Text(title).font(.system(size: 15, weight: .semibold)).foregroundColor(Color.zxF0); Text(desc).font(.system(size: 12)).foregroundColor(Color.zxF04) }; Spacer(); Image(systemName: "chevron.right").font(.system(size: 12)).foregroundColor(Color.zxF03) }.padding(16).background(Color.zxFill003).clipShape(RoundedRectangle(cornerRadius: 16)).overlay(RoundedRectangle(cornerRadius: 16).stroke(Color.zxBorder006, lineWidth: 1)) }.foregroundColor(.primary) } +} + +struct EditKnowledgePage: View { + @State private var title = "偏差-方差权衡"; @State private var content = "偏差衡量模型的预测与真实值之间的差异..." + var body: some View { + ZStack { Color.zxBg0.ignoresSafeArea() + VStack(spacing: 0) { + ZXBackHeader(title: "编辑知识点", subtitle: nil) {} + ScrollView { VStack(spacing: 16) { + VStack(alignment: .leading, spacing: 8) { Text("标题").font(.system(size: 12, weight: .semibold)).foregroundColor(Color.zxF035); TextField("", text: $title).font(.system(size: 15)).tint(Color.zxPurple).padding(.horizontal, 16).frame(height: 52).background(Color.zxFill004).clipShape(RoundedRectangle(cornerRadius: 14)).overlay(RoundedRectangle(cornerRadius: 14).stroke(Color.zxBorder008, lineWidth: 1)) } + VStack(alignment: .leading, spacing: 8) { Text("内容").font(.system(size: 12, weight: .semibold)).foregroundColor(Color.zxF035); TextEditor(text: $content).frame(minHeight: 200).scrollContentBackground(.hidden).padding(12).background(Color.zxFill004).clipShape(RoundedRectangle(cornerRadius: 14)).overlay(RoundedRectangle(cornerRadius: 14).stroke(Color.zxBorder008, lineWidth: 1)) } + Button {} label: { Text("保存修改").font(.system(size: 14, weight: .bold)).foregroundColor(.white).frame(maxWidth: .infinity).frame(height: 52).background(ZXGradient.ctaPurple).clipShape(RoundedRectangle(cornerRadius: 16)) } + }.padding(.horizontal, 20).padding(.bottom, 80) }.scrollIndicators(.hidden) + } + }.navigationBarHidden(true) + } +} diff --git a/AIStudyApp/AIStudyApp/Features/Profile/ProfileView.swift b/AIStudyApp/AIStudyApp/Features/Profile/ProfileView.swift new file mode 100644 index 0000000..951cf58 --- /dev/null +++ b/AIStudyApp/AIStudyApp/Features/Profile/ProfileView.swift @@ -0,0 +1,63 @@ +// +// ProfileView.swift - Page 10: Profile +// + +import SwiftUI + +struct ProfileView: View { + var body: some View { + ZStack { + ZXGradient.page.ignoresSafeArea() + ScrollView { + VStack(spacing: 16) { + HStack { + Text("我的").font(.system(size: 22, weight: .heavy)).foregroundColor(Color.zxF0).tracking(-0.5) + Spacer() + ZXIconBtn(icon: "bell", size: 36) {} + ZXIconBtn(icon: "gearshape", size: 36) {} + } + .padding(.horizontal, 20).padding(.top, ZXSpacing.statusBarH + 16).padding(.bottom, 4) + profileCard + VStack(spacing: 0) { + ZXProfileMenuRow(emoji: "🎯", title: "学习目标设置", desc: "调整你的学习目标") + ZXProfileMenuRow(emoji: "🔔", title: "复习提醒", desc: "间隔复习通知设置") + ZXProfileMenuRow(emoji: "📊", title: "学习报告", desc: "周报 · 月报 · 成就") + ZXProfileMenuRow(emoji: "🧩", title: "学习方法偏好", desc: "回忆 · 费曼 · 间隔") + ZXProfileMenuRow(emoji: "☁️", title: "数据同步与备份", desc: "云端同步设置") + } + .background(Color.zxFill004).clipShape(RoundedRectangle(cornerRadius: 20)) + .overlay(RoundedRectangle(cornerRadius: 20).stroke(Color.zxBorder006, lineWidth: 1)) + achievementsSection.padding(.bottom, 120) + } + .padding(.horizontal, 20) + } + .scrollIndicators(.hidden) + } + } + private var profileCard: some View { + VStack(spacing: 16) { + HStack { + ZStack { Circle().frame(width: 80, height: 80).foregroundColor(Color.zxPurpleBG(0.2)); Text("🧑‍🎓").font(.system(size: 36)) } + VStack(alignment: .leading, spacing: 4) { Text("学习者").font(.system(size: 20, weight: .bold)).foregroundColor(Color.zxF0); Text("user@example.com").font(.system(size: 12)).foregroundColor(Color.zxF04) } + Spacer(); Image(systemName: "chevron.right").font(.system(size: 14)).foregroundColor(Color.zxF03) + } + HStack(spacing: 0) { ZXProfileStat(value: "14", label: "连续天", color: Color.zxOrange); ZXProfileStat(value: "47", label: "知识点", color: Color.zxPurple); ZXProfileStat(value: "1,240", label: "积分", color: Color.zxTeal) } + } + .padding(20).background(ZXGradient.profileCard).overlay(RoundedRectangle(cornerRadius: 20).stroke(Color(hex: "#7C6EFA", opacity: 0.2), lineWidth: 1)).clipShape(RoundedRectangle(cornerRadius: 20)) + } + private var achievementsSection: some View { + VStack(alignment: .leading, spacing: 12) { + Text("成就").font(.system(size: 15, weight: .bold)).foregroundColor(Color.zxF0) + HStack(spacing: 8) { ZXAchievementBadge(emoji: "🔥", label: "连续 14 天", color: Color.zxOrange); ZXAchievementBadge(emoji: "🧠", label: "费曼达人", color: Color.zxPurple); ZXAchievementBadge(emoji: "📚", label: "知识收藏家", color: Color.zxTeal); ZXAchievementBadge(emoji: "⚡", label: "速学者", color: Color.zxYellow) } + } + } +} +struct ZXProfileStat: View { let v: String; let l: String; let c: Color; var body: some View { VStack(spacing: 2) { Text(v).font(.system(size: 18, weight: .bold)).foregroundColor(c); Text(l).font(.system(size: 11)).foregroundColor(Color.zxF04) }.frame(maxWidth: .infinity) } + init(value: String, label: String, color: Color) { self.v = value; self.l = label; self.c = color } +} +struct ZXProfileMenuRow: View { let emoji: String; let title: String; let desc: String + var body: some View { HStack(spacing: 12) { Text(emoji).font(.system(size: 20)).frame(width: 36, height: 36).background(Color.zxFill006).clipShape(RoundedRectangle(cornerRadius: 10)); VStack(alignment: .leading, spacing: 2) { Text(title).font(.system(size: 14, weight: .semibold)).foregroundColor(Color.zxF0); Text(desc).font(.system(size: 11)).foregroundColor(Color.zxF03) }; Spacer(); Image(systemName: "chevron.right").font(.system(size: 12)).foregroundColor(Color.zxF03) }.padding(.horizontal, 16).padding(.vertical, 14) } +} +struct ZXAchievementBadge: View { let emoji: String; let label: String; let color: Color + var body: some View { VStack(spacing: 6) { Text(emoji).font(.system(size: 24)).frame(width: 48, height: 48).background(color.opacity(0.12)).clipShape(RoundedRectangle(cornerRadius: 14)).overlay(RoundedRectangle(cornerRadius: 14).stroke(color.opacity(0.25), lineWidth: 1)); Text(label).font(.system(size: 10, weight: .semibold)).foregroundColor(Color.zxF04) }.frame(maxWidth: .infinity) } +} diff --git a/AIStudyApp/AIStudyApp/Features/Study/StudyHomeView.swift b/AIStudyApp/AIStudyApp/Features/Study/StudyHomeView.swift new file mode 100644 index 0000000..7d1dbdb --- /dev/null +++ b/AIStudyApp/AIStudyApp/Features/Study/StudyHomeView.swift @@ -0,0 +1,50 @@ +// +// StudyHomeView.swift - Page 8 +// + +import SwiftUI + +struct StudyHomeView: View { + @State private var ts: [ZXSTask] = [ + .init(t: "机器学习 - 回忆测试", tp: "回忆测试", c: Color.zxPurple, m: 10, d: true), + .init(t: "高数 - 间隔复习 8 题", tp: "间隔复习", c: Color.zxOrange, m: 15, d: true), + .init(t: "英语词汇 - 25 个待复习", tp: "词汇复习", c: Color.zxTeal, m: 8, d: false), + .init(t: "注意力机制 - 费曼解释", tp: "费曼练习", c: Color.zxAccent, m: 12, d: false), + .init(t: "产品设计 - 薄弱点复习", tp: "薄弱点", c: Color.zxYellow, m: 10, d: false), + ] + private let wb: [CGFloat] = [0.3, 0.7, 1.0, 0.4, 0.9, 0.6, 0.2] + private let dl = ["一","二","三","四","五","六","日"] + + var body: some View { + ZStack { ZXGradient.page.ignoresSafeArea() + ScrollView { VStack(spacing: 16) { + HStack { VStack(alignment: .leading, spacing: 2) { Text("周四,1月16日").font(.system(size: 12, weight: .medium)).foregroundColor(Color.zxF04); Text("学习工作台").font(.system(size: 20, weight: .heavy)).foregroundColor(Color.zxF0).tracking(-0.4) }; Spacer() + HStack(spacing: 4) { Image(systemName: "flame.fill").font(.system(size: 14)).foregroundColor(Color.zxOrange); Text("14 天连续").font(.system(size: 13, weight: .bold)).foregroundColor(Color.zxOrange) }.padding(.horizontal, 12).padding(.vertical, 6).background(Color.zxOrangeBG(0.1)).clipShape(Capsule()).overlay(Capsule().stroke(Color(hex: "#F97316", opacity: 0.2), lineWidth: 1)) } + .padding(.horizontal, 20).padding(.top, ZXSpacing.statusBarH + 16).padding(.bottom, 4) + pc + VStack(alignment: .leading, spacing: 12) { HStack { Text("今日任务").font(.system(size: 15, weight: .bold)).foregroundColor(Color.zxF0); Spacer(); HStack(spacing: 4) { Image(systemName: "calendar").font(.system(size: 12)).foregroundColor(Color.zxF04); Text("AI 自动排期").font(.system(size: 12)).foregroundColor(Color.zxF04) } } + ForEach($ts) { $t in ZXSTaskRow(task: t) { t.d.toggle() } } } + VStack(alignment: .leading, spacing: 14) { Text("本周学习活跃").font(.system(size: 15, weight: .bold)).foregroundColor(Color.zxF0) + HStack(alignment: .bottom, spacing: 8) { ForEach(0..<7, id: \.self) { i in VStack(spacing: 8) { RoundedRectangle(cornerRadius: 6).fill(i == 6 ? Color.zxFill01 : Color(hex: "#7C6EFA", opacity: wb[i] * 0.9 + 0.1)).frame(height: wb[i] * 60); Text(dl[i]).font(.system(size: 10, weight: i == 2 ? .bold : .regular)).foregroundColor(i == 2 ? Color.zxPurple : Color.zxF03) }.frame(maxWidth: .infinity) } } + HStack { Text("总计 3.5 小时").font(.system(size: 11)).foregroundColor(Color.zxF03); Spacer(); Text("日均 30 分钟").font(.system(size: 11)).foregroundColor(Color.zxF03) } } + .padding(.bottom, 120) } + .padding(.horizontal, 20) } + .scrollIndicators(.hidden) } + } + private var pc: some View { let dn = ts.filter(\.d).count; let pct = CGFloat(dn) / 5 + return VStack(spacing: 12) { HStack { VStack(alignment: .leading, spacing: 2) { Text("今日进度").font(.system(size: 13, weight: .medium)).foregroundColor(Color.zxF05); HStack(alignment: .lastTextBaseline, spacing: 6) { Text("\(dn)").font(.system(size: 26, weight: .black)).foregroundColor(Color.zxF0); Text("/ 5"); Text("个任务").font(.system(size: 14, weight: .medium)).foregroundColor(Color.zxF04) } }; Spacer() + ZStack { Circle().trim(from: 0, to: pct).stroke(ZXGradient.brand, style: StrokeStyle(lineWidth: 8, lineCap: .round)).rotationEffect(.degrees(-90)).frame(width: 64, height: 64); Text("\(Int(pct * 100))%").font(.system(size: 14, weight: .heavy)).foregroundColor(Color.zxPurple) } } + ZStack(alignment: .leading) { RoundedRectangle(cornerRadius: 3).fill(Color.zxFill008).frame(height: 6); RoundedRectangle(cornerRadius: 3).fill(LinearGradient(colors: [Color.zxPurple, Color.zxAccent], startPoint: .leading, endPoint: .trailing)).frame(width: max(6, pct * (UIScreen.main.bounds.width - 72)), height: 6) } + HStack { VStack(alignment: .leading, spacing: 2) { Text("\(dn * 12) 分钟").font(.system(size: 13, weight: .bold)).foregroundColor(Color.zxF0); Text("已学").font(.system(size: 10)).foregroundColor(Color.zxF04) }; Spacer(); VStack(spacing: 2) { Text("\((5 - dn) * 11) 分钟").font(.system(size: 13, weight: .bold)).foregroundColor(Color.zxF0); Text("剩余").font(.system(size: 10)).foregroundColor(Color.zxF04) }; Spacer(); VStack(alignment: .trailing, spacing: 2) { Text("+5 点").font(.system(size: 13, weight: .bold)).foregroundColor(Color.zxF0); Text("掌握").font(.system(size: 10)).foregroundColor(Color.zxF04) } } } + .padding(16).background(ZXGradient.progressCard).overlay(RoundedRectangle(cornerRadius: 20).stroke(Color(hex: "#7C6EFA", opacity: 0.15), lineWidth: 1)).clipShape(RoundedRectangle(cornerRadius: 20)) } +} + +struct ZXSTask: Identifiable { let id = UUID(); let t: String; let tp: String; let c: Color; let m: Int; var d: Bool } +struct ZXSTaskRow: View { let task: ZXSTask; var action: () -> Void + var body: some View { Button(action: action) { HStack(spacing: 12) { Image(systemName: task.d ? "checkmark.circle.fill" : "circle").font(.system(size: 20)).foregroundColor(task.d ? Color.zxGreen : Color.zxF02) + VStack(alignment: .leading, spacing: 4) { Text(task.t).font(.system(size: 13, weight: .semibold)).foregroundColor(task.d ? Color.zxF04 : Color.zxF0).strikethrough(task.d); HStack(spacing: 8) { Text(task.tp).font(.system(size: 10, weight: .semibold)).foregroundColor(task.c).padding(.horizontal, 6).padding(.vertical, 1).background(task.c.opacity(0.12)).clipShape(Capsule()); Text("约 \(task.m) 分钟").font(.system(size: 10)).foregroundColor(Color.zxF035) } } + Spacer(); if !task.d { Image(systemName: "play.fill").font(.system(size: 14)).foregroundColor(.white).frame(width: 32, height: 32).background(ZXGradient.brand).clipShape(RoundedRectangle(cornerRadius: 10)) } } + .padding(.horizontal, 16).padding(.vertical, 12).background(task.d ? Color.zxFill003 : Color.zxFill005).overlay(RoundedRectangle(cornerRadius: 14).stroke(task.d ? Color(hex: "#FFFFFF", opacity: 0.05) : Color.zxBorder008, lineWidth: 1)).clipShape(RoundedRectangle(cornerRadius: 14)).opacity(task.d ? 0.6 : 1) }.foregroundColor(.primary) } +} + +extension Color { static let zxF035 = Color(hex: "#F0F0FF", opacity: 0.35) } diff --git a/AIStudyApp/README.md b/AIStudyApp/README.md new file mode 100644 index 0000000..e43acf7 --- /dev/null +++ b/AIStudyApp/README.md @@ -0,0 +1,112 @@ +# 知习 ZhiXi — iOS App + +AI-first 系统化学习 App,SwiftUI + 深色主题。 + +## 导航关系 + +``` +Splash(2s 自动跳转) + ↓ +Welcome(欢迎页)→ 已有账号 → Login + ↓ 开始使用 +Login(手机号/邮箱登录) + ↓ 登录成功 +Onboarding(4 步引导:输入知识 / 主动输出 / AI 分析 / 掌握知识) + ↓ 下一步 +GoalSetup(学习目标 / 方法 / 时间) + ↓ 开始学习 +┌──────────────────────────────────────────────────────┐ +│ 5-Tab 主界面 │ +│ │ +│ [AI] [知识库] [学习] [分析] [我的] │ +│ │ │ │ │ │ │ +│ ├─ 开始回答 ├─ 卡片点击 ├─ 任务点击 │ │ │ +│ │ → Daily │ → Detail │ → Recall │ │ │ +│ │ Thinking │ │ / Feedbk │ │ │ +│ │ ├─ 创建 │ │ │ │ +│ ├─ 快捷操作 │ → Create │ │ │ │ +│ │ → Recall │ │ │ │ │ +│ │ / Weak │ ┌ 知识点 │ │ │ │ +│ │ │ ├ Detail │ │ │ │ +│ └─ 互动列表 │ │ → KnwlD │ │ │ │ +│ → AIChat │ ├ Add │ │ │ │ +│ │ ├ Import │ │ │ │ +│ │ └ Edit │ │ │ │ +└──────────────────────────────────────────────────────┘ +``` + +## 页面清单(对照 React 原型 22 页) + +| # | 页面 | 文件 | 状态 | +|---|------|------|------| +| 1 | Splash 启动页 | `AIStudyAppApp.swift` → `SplashPage` | ✅ | +| 2 | Welcome 欢迎页 | `AIStudyAppApp.swift` → `WelcomePage` | ✅ | +| 3 | Login 登录页 | `AIStudyAppApp.swift` → `LoginPage` | ✅ | +| 4 | Onboarding 引导页 | `AIStudyAppApp.swift` → `OnboardingPage` | ✅ | +| 5 | GoalSetup 目标设置 | `AIStudyAppApp.swift` → `GoalSetupPage` | ✅ | +| 6 | AIHome AI 首页 | `Features/AI/AIHomeView.swift` | ✅ | +| 7 | LibraryHome 知识库首页 | `Features/Library/LibraryHomeView.swift` | ✅ | +| 8 | StudyHome 学习工作台 | `Features/Study/StudyHomeView.swift` | ✅ | +| 9 | AnalysisHome 学习分析 | `Features/Analysis/AnalysisHomeView.swift` | ✅ | +| 10 | Profile 我的 | `Features/Profile/ProfileView.swift` | ✅ | +| 11 | AIChat AI 对话 | `Features/AI/DailyThinkingPage.swift` → `AIChatPage` | ✅ | +| 14 | DailyThinking 今日思考 | `Features/AI/DailyThinkingPage.swift` | ✅ | +| 13 | RecallTest 回忆测试 | `Features/AI/DailyThinkingPage.swift` → `RecallTestPage` | ✅ | +| 16 | WeakPoints 薄弱点分析 | `Features/AI/DailyThinkingPage.swift` → `WeakPointsPage` | ✅ | +| 15 | AIFeedback AI 反馈 | `Features/AI/DailyThinkingPage.swift` → `AIFeedbackPageView` | ✅ | +| 18 | CreateLibrary 创建知识库 | `Features/Library/LibrarySubpages.swift` | ✅ | +| 19 | LibraryDetail 知识库详情 | `Features/Library/LibrarySubpages.swift` | ✅ | +| 20 | AddKnowledge 添加知识点 | `Features/Library/LibrarySubpages.swift` | ✅ | +| 21 | Import 导入资料 | `Features/Library/LibrarySubpages.swift` | ✅ | +| 22 | KnowledgeDetail 知识点详情 | `Features/Library/LibrarySubpages.swift` | ✅ | +| 23 | EditKnowledge 编辑知识点 | `Features/Library/LibrarySubpages.swift` | ✅ | + +## 项目结构 + +``` +AIStudyApp/ +├── AIStudyAppApp.swift # 根路由 + Splash/Welcome/Login/Onboarding/GoalSetup +├── ContentView.swift # 5-Tab 主界面 + ZXTabBar + ZXIconBtn + ZXScoreBox + ZXAIInputBar +├── Core/ +│ └── DesignSystem/ +│ └── DesignTokens.swift # 颜色 / 渐变 / 圆角 / 间距 / 字号 +└── Features/ + ├── AI/ + │ ├── AIHomeView.swift # AI 首页 + │ └── DailyThinkingPage.swift # 今日思考 / AI 对话 / 回忆测试 / 薄弱点 / AI 反馈 + ├── Library/ + │ ├── LibraryHomeView.swift # 知识库首页 + │ └── LibrarySubpages.swift # 创建 / 详情 / 添加 / 导入 / 知识点详情 / 编辑 + ├── Study/ + │ └── StudyHomeView.swift # 学习工作台 + ├── Analysis/ + │ └── AnalysisHomeView.swift # 学习分析 + └── Profile/ + └── ProfileView.swift # 我的页 +``` + +## 设计系统 + +| 类别 | Token | 值 | +|------|-------|-----| +| 主背景 | `Color.zxBg0` | `#0F0F1A` | +| 页面渐变 | `ZXGradient.page` | `#0F0F1A → #12122A` | +| 品牌紫 | `Color.zxPurple` | `#7C6EFA` | +| 品牌橙 | `Color.zxOrange` | `#F97316` | +| 文字主色 | `Color.zxF0` | `#F0F0FF` | +| 卡片圆角 | `ZXRadius.xl3` | `20` | +| 按钮高度 | `ZXSize.buttonH` | `42` | +| 页面水平间距 | `ZXSpacing.pageHPadding` | `20` | +| 状态栏高度 | `ZXSpacing.statusBarH` | `44` | +| TabBar 高度 | `ZXSpacing.tabBarH` | `83` | + +以上全部从 React 原型 1:1 像素级提取。 + +## 运行 + +Xcode 打开 `AIStudyApp.xcodeproj`,选择 iPhone 17 Pro 模拟器,`Cmd+R`。 + +``` +Clean Build 之前先: +rm -rf ~/Library/Developer/Xcode/DerivedData/AIStudyApp-* +``` diff --git a/ZhiXi_App/ai_assistant_workspace_v2/code.html b/ZhiXi_App/ai_assistant_workspace_v2/code.html deleted file mode 100644 index ec61686..0000000 --- a/ZhiXi_App/ai_assistant_workspace_v2/code.html +++ /dev/null @@ -1,282 +0,0 @@ - - - - - -AI 助手 - 知习 - - - - - - - - - - -
- -
-
- -
-AI 助手 - - - 当前学习:认知心理学 · 记忆的编码与提取 - -
-
- -
- -
- -
-
-auto_awesome -
-

准备好深入探索了吗?

-

针对「认知心理学 · 记忆的编码与提取」,我可以为你提供更深度的解析或测验。

-
- -
- -
-
-

关于“情境依存性记忆”,如果我在图书馆复习,考试时在教室,是不是会影响提取?

-
-
- -
-
-colors_spark -
-
-
-

- 这是一个非常经典的问题!是的,根据**情境依存性记忆 (Context-Dependent Memory)** 的原理,外部环境线索确实会影响记忆的提取。 -

- -
-

核心机制

-

- 当你编码(学习)信息时,周围的环境(如气味、光线、声音,甚至背景音乐)会一并被编码为提取线索。如果提取(考试)时的环境与编码时一致,这些线索能帮助你更容易地唤起记忆。这就是著名的“潜水员实验”所证明的。 -

-
-

- 为了克服这种环境差异带来的影响,你可以尝试**多情境复习**,或者在脑海中**主动重构**学习时的情境。 -

-
- -
- - - -
-
-
-
-
- -
- -
-
- - - - -
-
- -
- -
- -
-
- - -
-
-
- - - - - \ No newline at end of file diff --git a/ZhiXi_App/ai_assistant_workspace_v2/screen.png b/ZhiXi_App/ai_assistant_workspace_v2/screen.png deleted file mode 100644 index 6e363a2..0000000 Binary files a/ZhiXi_App/ai_assistant_workspace_v2/screen.png and /dev/null differ diff --git a/ZhiXi_App/ai_learning_analysis_v2/code.html b/ZhiXi_App/ai_learning_analysis_v2/code.html deleted file mode 100644 index e012743..0000000 --- a/ZhiXi_App/ai_learning_analysis_v2/code.html +++ /dev/null @@ -1,306 +0,0 @@ - - - - - -本次学习分析 - - - - - - - - - -
-
- -
-

本次学习分析

-
- -
-
- -
- -
-
-
-auto_awesome -AI Insight -
-

- 你已经初步掌握了神经网络的基础架构,但在激活函数的作用上还有待加强。 -

-
- -
- -
-

掌握度

-
- - - - - - - - - - - - - -
-85% -优秀 -
-
-
- -
-
-update -

下一次复习建议

-
-
-
-

明天上午 10:00

-

根据艾宾浩斯遗忘曲线,此时复习效果最佳。

-
-
-
- -
- -
-
-
-check -
-

优势

-
-
    -
  • - - 概念回忆清晰 -
  • -
  • - - 架构逻辑准确 -
  • -
-
- -
-
-
-priority_high -
-

薄弱点

-
-
    -
  • - - ReLU 与 Sigmoid 的差异理解 -
  • -
  • - - 反向传播推导细节 -
  • -
-
-
- -
-
-lightbulb -

改进建议

-
-
-
-menu_book -
-

重读章节 4.2 (间隔复习)

-

深入理解激活函数的非线性特征,建议使用费曼技巧向自己解释。

-
-
-
-quiz -
-

尝试一次练习测验

-

通过 5 道针对性题目巩固记忆。

-
-
-
-
-
- -
- - -
-
- - - \ No newline at end of file diff --git a/ZhiXi_App/ai_learning_analysis_v2/screen.png b/ZhiXi_App/ai_learning_analysis_v2/screen.png deleted file mode 100644 index 8353759..0000000 Binary files a/ZhiXi_App/ai_learning_analysis_v2/screen.png and /dev/null differ diff --git a/ZhiXi_App/ethereal_intelligence/DESIGN.md b/ZhiXi_App/ethereal_intelligence/DESIGN.md deleted file mode 100644 index 5a1408b..0000000 --- a/ZhiXi_App/ethereal_intelligence/DESIGN.md +++ /dev/null @@ -1,145 +0,0 @@ ---- -name: Ethereal Intelligence -colors: - surface: '#f9f9fd' - surface-dim: '#d9dade' - surface-bright: '#f9f9fd' - surface-container-lowest: '#ffffff' - surface-container-low: '#f3f3f7' - surface-container: '#ededf1' - surface-container-high: '#e8e8ec' - surface-container-highest: '#e2e2e6' - on-surface: '#1a1c1f' - on-surface-variant: '#414751' - inverse-surface: '#2e3034' - inverse-on-surface: '#f0f0f4' - outline: '#717783' - outline-variant: '#c1c7d3' - surface-tint: '#0060ac' - primary: '#005da7' - on-primary: '#ffffff' - primary-container: '#2976c7' - on-primary-container: '#fdfcff' - inverse-primary: '#a4c9ff' - secondary: '#8135c5' - on-secondary: '#ffffff' - secondary-container: '#ba70ff' - on-secondary-container: '#440076' - tertiary: '#4648d4' - on-tertiary: '#ffffff' - tertiary-container: '#6063ee' - on-tertiary-container: '#fffbff' - error: '#ba1a1a' - on-error: '#ffffff' - error-container: '#ffdad6' - on-error-container: '#93000a' - primary-fixed: '#d4e3ff' - primary-fixed-dim: '#a4c9ff' - on-primary-fixed: '#001c39' - on-primary-fixed-variant: '#004883' - secondary-fixed: '#f0dbff' - secondary-fixed-dim: '#deb7ff' - on-secondary-fixed: '#2c0050' - on-secondary-fixed-variant: '#670fac' - tertiary-fixed: '#e1e0ff' - tertiary-fixed-dim: '#c0c1ff' - on-tertiary-fixed: '#07006c' - on-tertiary-fixed-variant: '#2f2ebe' - background: '#f9f9fd' - on-background: '#1a1c1f' - surface-variant: '#e2e2e6' -typography: - h1: - fontFamily: Manrope - fontSize: 34px - fontWeight: '700' - lineHeight: '1.2' - letterSpacing: -0.02em - h2: - fontFamily: Manrope - fontSize: 28px - fontWeight: '600' - lineHeight: '1.3' - letterSpacing: -0.01em - h3: - fontFamily: Manrope - fontSize: 22px - fontWeight: '600' - lineHeight: '1.3' - letterSpacing: '0' - body-lg: - fontFamily: Manrope - fontSize: 17px - fontWeight: '400' - lineHeight: '1.6' - letterSpacing: -0.01em - body-md: - fontFamily: Manrope - fontSize: 15px - fontWeight: '400' - lineHeight: '1.5' - letterSpacing: '0' - label-caps: - fontFamily: Manrope - fontSize: 12px - fontWeight: '600' - lineHeight: '1.0' - letterSpacing: 0.05em -rounded: - sm: 0.25rem - DEFAULT: 0.5rem - md: 0.75rem - lg: 1rem - xl: 1.5rem - full: 9999px -spacing: - base: 8px - xs: 4px - sm: 12px - md: 24px - lg: 40px - xl: 64px - container-margin: 20px - gutter: 16px ---- - -## Brand & Style -The brand personality is centered on "Enlightened Learning"—a synthesis of high-tech AI capabilities and human-centric intuition. This design system targets a young, intellectually curious demographic that values depth over speed. - -The visual style merges **Apple Minimalism** with **Futuristic Glassmorphism**. It prioritizes extreme "breathability" and a sense of calm focus. The UI should feel like a quiet, high-tech sanctuary for thought. Key attributes include high-blur translucency, expansive negative space, and a "light-as-air" depth model that avoids heavy physical metaphors in favor of ethereal, glowing digital surfaces. - -## Colors -The palette is anchored by a luminous off-white background to reduce eye strain and establish a premium feel. The primary and secondary colors are used sparingly for interactive elements and brand accents. - -To represent the "AI-first" nature of the product, use a signature gradient (the "AI Glow") for progress indicators, active states, and special AI-driven insights. Functional neutrals follow Apple’s grayscale hierarchy but use a slight blue tint in the shadows to maintain the cool, sophisticated atmosphere. - -## Typography -Manrope provides a modern, geometric clarity that feels more "tech-forward" than standard San Francisco while maintaining excellent legibility. - -The typographic hierarchy emphasizes whitespace through generous line heights (1.5x - 1.6x for body text). Headlines use slightly tighter tracking to feel "locked" and authoritative, while labels use expanded tracking for a premium, airy feel. Use "Ink-Black" (#1D1D1F) for primary text to ensure high contrast without the harshness of pure black. - -## Layout & Spacing -The layout follows a fluid, safe-area-driven model typical of iOS, but with significantly increased vertical margins to promote the "calm" atmosphere. - -A 4-column grid is used for mobile portrait views. Elements should never feel "packed." Use the `lg` (40px) and `xl` (64px) spacing units to separate major content sections, creating a sense of luxury and focus. Component internal padding should be generous, typically starting at `md` (24px) for cards and containers. - -## Elevation & Depth -This design system utilizes **Luminous Stratification** rather than traditional heavy shadows. - -- **The Base:** The #FBFBFF background acts as the canvas. -- **Glassmorphism:** AI-interaction zones and input fields use a high-saturation backdrop blur (20px-30px) with a 1px semi-transparent white border (0.1 opacity) to simulate frosted glass. -- **Gentle Shadows:** Floating cards use "Ambient Shadows"—extremely diffused (40px-60px blur), low-opacity (5%) shadows with a subtle tint of the primary blue to prevent a "dirty" look. -- **AI Glow:** The highest level of depth is reserved for active AI processes, which emit a soft, localized outer glow using the signature gradient. - -## Shapes -Shapes follow a "Squircle" logic to align with Apple’s hardware and software aesthetic. - -Standard components (buttons, small cards) use a 16px (1rem) radius. Larger container cards use a 24px (1.5rem) radius. Interactive inputs use a fully rounded "pill" shape or the 16px standard to maintain consistency. The goal is to avoid sharp corners entirely, reinforcing the "human-centric" and "soft" brand personality. - -## Components -- **Buttons:** Primary buttons use the AI Glow gradient with white text. Secondary buttons are glass-morphic with a 1px border. All buttons have a high-press scale effect (shrink to 0.96) for tactile feedback. -- **Input Fields:** These are the centerpiece of the "AI-first" experience. Use a high-blur glass background with a subtle inner shadow. Place the cursor/caret in the primary blue. -- **Cards:** Content is housed in "Floating Containers"—white or ultra-light gray backgrounds with the previously defined ambient shadows and no visible border. -- **Thin-Stroke Icons:** Use a consistent 1.5pt or 2pt stroke weight. Icons should be open-ended and minimalist, avoiding fills unless in an active state. -- **AI Learning Progress:** Use a custom "Liquid Trace" component—a thin, glowing gradient line that pulses gently as the user moves through deep-learning modules. -- **Chips/Tags:** Small, pill-shaped elements with light blue or violet tinted backgrounds (0.1 opacity) and matching colored text. \ No newline at end of file diff --git a/ZhiXi_App/knowledge_journey_v2/code.html b/ZhiXi_App/knowledge_journey_v2/code.html deleted file mode 100644 index 50e4479..0000000 --- a/ZhiXi_App/knowledge_journey_v2/code.html +++ /dev/null @@ -1,275 +0,0 @@ - - - - - -知习 - 知识库 - - - - - - - - - - -
-
-
-User profile avatar -
-

知习

- -
-
-
- -
-
-

知识库

-

探索您的学习路径与认知架构

-
- -
-search - -
-
- -
-
- -
-
-
-当前焦点 -

认知心理学

-

深入理解人类心智的信息处理机制,掌握高效学习的底层逻辑。

-
-
-psychology -
-
-
-
-
-
-
-
-35% 完成 -
-
-
- -
-

学习旅程

-
- -
-
- -
-
-
-check_circle -
-
-
-
-第 1 天 -主动回忆 -
-

认知基础:感知与模式识别

-
-
- -
-
- -
-
-target -
-
-
-
-
-第 2 天 • 当前 -检索练习 -
-

注意力机制:焦点与过滤

-

探讨选择性注意和分配性注意,理解多任务处理的神话与现实。

- -
-
-
- -
-
-
-lock -
-
-
-第 3 天 -

记忆模型:工作与长期记忆

-
-
- -
-
-
-
-
-
-
-第 4 天 -

知识表征:心智模型

-
-
-
-
-
-
- - - \ No newline at end of file diff --git a/ZhiXi_App/knowledge_journey_v2/screen.png b/ZhiXi_App/knowledge_journey_v2/screen.png deleted file mode 100644 index bbab9e3..0000000 Binary files a/ZhiXi_App/knowledge_journey_v2/screen.png and /dev/null differ diff --git a/ZhiXi_App/learning_workspace_v2/code.html b/ZhiXi_App/learning_workspace_v2/code.html deleted file mode 100644 index 7cb0b99..0000000 --- a/ZhiXi_App/learning_workspace_v2/code.html +++ /dev/null @@ -1,322 +0,0 @@ - - - - - -ZhiXi - 学习 - - - - - - - - - - -
-
- -

知习

-
- -
-
- -
-

学习

-

让每一次学习都进入闭环

-
- -
-input 输入 -arrow_forward -output 输出 -arrow_forward -feedback 反馈 -arrow_forward -cycle 复习 -
- -
- -
-
-
-
-今日重点 - -timer 20 mins - -
-

认知心理学:记忆的编码与提取

-

进入知识内化的关键阶段,建议通过主动回忆来加强神经连接。

-
-
- -
-
- -
-
-psychiatry -
-

先试着用自己的话说出来

-

不要急着看答案,先回忆一次

- -
- -
-

-target 需要再巩固的地方 -

-
    -
  • -smart_toy -
    -

    ReLU vs Sigmoid difference

    -

    AI 诊断:在上次测验中概念混淆

    -
    -
  • -
-
- -
-

-history_edu 今天适合复习 -

-
- -
-
-

微观经济学基础

-
-trending_down 记忆曲线衰减预警 -
-
- -
- -
-
-

线性代数核心定理

-
-schedule 距上次复习 3 天 -
-
- -
-
-
-
-
- - - \ No newline at end of file diff --git a/ZhiXi_App/learning_workspace_v2/screen.png b/ZhiXi_App/learning_workspace_v2/screen.png deleted file mode 100644 index 36ab6f1..0000000 Binary files a/ZhiXi_App/learning_workspace_v2/screen.png and /dev/null differ diff --git a/ZhiXi_App/lesson_active_recall/code.html b/ZhiXi_App/lesson_active_recall/code.html deleted file mode 100644 index 15887f7..0000000 --- a/ZhiXi_App/lesson_active_recall/code.html +++ /dev/null @@ -1,239 +0,0 @@ - - - - - -知习 - 认知心理学 · 记忆 - - - - - - - - - - -
- -
-

认知心理学 · 记忆

-
- -
-
- -
-
-

理解编码与提取

-
- -
-

-flag - 本节目标 -

-
    -
  • -
    -

    区分记忆的三个主要阶段:编码、存储和提取。

    -
  • -
  • -
    -

    解释“深度加工”理论如何影响信息留存率。

    -
  • -
  • -
    -

    掌握至少两种利用上下文线索促进记忆提取的方法。

    -
  • -
-
- -
-

- 在认知心理学中,记忆通常被概念化为一个信息处理系统,类似于计算机。这个过程可以划分为三个基本且连续的阶段:编码(Encoding)、存储(Storage)和提取(Retrieval)。 -

-

-编码是将外部感官信息转化为大脑能够处理和存储的神经表征的过程。这就如同我们在键盘上输入文字,将其转化为计算机能识别的代码。编码的质量直接决定了后续记忆的牢固程度。 -

- -
-
-lightbulb -核心概念 : 深度加工理论 (Levels of Processing) -
-

- 由 Craik 和 Lockhart (1972) 提出,该理论认为信息的留存不仅仅取决于我们重复它的次数,更取决于我们加工它的“深度”。关注词语的含义(语义加工)比仅仅关注它的声音或外观能产生更强韧的记忆痕迹。 -

-
-

- 当我们试图回想某事时,我们进入了提取阶段。提取是从记忆库中搜寻并唤起已存储信息的过程。提取的成功不仅依赖于信息是否完好地存储,还很大程度上依赖于是否存在合适的“提取线索”(Retrieval Cues)。 -

-
-
-
- -
-
-

现在试着回忆一下

-

请不用翻看上面的内容,尝试用自己的话解释刚刚的知识点。

-
- -
- - -
- -
-
- -
- - - -
- -
- -
-
-
- \ No newline at end of file diff --git a/ZhiXi_App/lesson_active_recall/screen.png b/ZhiXi_App/lesson_active_recall/screen.png deleted file mode 100644 index 6b3a96b..0000000 Binary files a/ZhiXi_App/lesson_active_recall/screen.png and /dev/null differ diff --git a/ZhiXi_App/my_learning_growth_profile/code.html b/ZhiXi_App/my_learning_growth_profile/code.html deleted file mode 100644 index 405a3b8..0000000 --- a/ZhiXi_App/my_learning_growth_profile/code.html +++ /dev/null @@ -1,394 +0,0 @@ - - - - - -ZhiXi - Profile & Settings - - - - - - - - - - -
- - - - - - - - - - - - - - -
- -
-
-
-
-person -
-
-
知习
- -
-
-
- -
-
-Profile Photo -
-

Alex Mercer

-

今天也在把知识变清楚

-
- -
-
-
-local_fire_department -
-

14

-

Learning Streak

-
-
-
-calendar_month -
-

24

-

Studies this Month

-
-
-
-all_inclusive -
-

12

-

Closed Loops

-
-
-
-schedule -
-

128 hrs

-

Total Study

-
-
- -
-
-
-

学习活跃图

-

过去 90 天,你的学习投入记录

-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-Less -
-
-
-
-
-
-
-More -
-
- -

待巩固项

-
-
-
-
-

记忆曲线

-

认知心理学基础

-
-Pending -
-
-
-auto_awesome -

今晚复习一次,并尝试主动回忆

-
-
- -
-
-
-
-

激活函数的作用

-

高级神经网络

-
-Added to Review -
-
-
-auto_awesome -

用一个例子解释它为什么必要

-
-
- -
-
- -

最近学习记录

-
-
-
-
-
-check_circle -
-
-

完成一次主动回忆

-

认知心理学 · 23 分钟

-
-
-
-
-analytics -
-
-

生成 AI 分析

-

神经网络基础

-
-
-
-
-history_edu -
-
-

复习了 2 个待巩固项

-

微积分速成

-
-
-
-
-
- - - \ No newline at end of file diff --git a/ZhiXi_App/my_learning_growth_profile/screen.png b/ZhiXi_App/my_learning_growth_profile/screen.png deleted file mode 100644 index 6f17223..0000000 Binary files a/ZhiXi_App/my_learning_growth_profile/screen.png and /dev/null differ diff --git a/ZhiXi_App/zhixi_ai_home_v2/code.html b/ZhiXi_App/zhixi_ai_home_v2/code.html deleted file mode 100644 index 3eecf06..0000000 --- a/ZhiXi_App/zhixi_ai_home_v2/code.html +++ /dev/null @@ -1,267 +0,0 @@ - - - - - -知习 - AI-First Learning Home - - - - - - - - - -
-
- -
-
-

知习

-

今天想把哪块知识变清楚?

-
-
- -
- -
-
- -
-
-
- - -
- -
-
- -
- - - - -
- -
-
-
-Current Learning -more_horiz -
-

认知心理学基础

-
-lightbulb -
-AI Suggestion -

建议今天先复习记忆编码部分

-
-
-
-
-
- -
- -
- \ No newline at end of file diff --git a/ZhiXi_App/zhixi_ai_home_v2/screen.png b/ZhiXi_App/zhixi_ai_home_v2/screen.png deleted file mode 100644 index 6d784ed..0000000 Binary files a/ZhiXi_App/zhixi_ai_home_v2/screen.png and /dev/null differ