feat 完成项目基础构架

This commit is contained in:
2025-03-19 09:13:18 +08:00
parent 74d2c6567a
commit e17871e53d
19 changed files with 2402 additions and 85 deletions

View File

@@ -0,0 +1,221 @@
//
// CourseFormView.swift
// TeachMate
//
// Created by Hongli on 2025/3/18.
//
import SwiftUI
import SwiftData
//
struct CourseFormView: View {
@Environment(\.dismiss) private var dismiss
@Environment(\.modelContext) private var modelContext
@Environment(\.colorScheme) private var colorScheme
@Query private var semesters: [Semester]
enum FormMode {
case add
case edit(CourseModel)
}
let mode: FormMode
let onSave: (CourseModel) -> Void
@State private var name: String = ""
@State private var selectedColor: String = "#FCF3CF" //
@State private var selectedSemester: Semester?
//
private let colorOptions = [
"#FCF3CF", //
"#D6EAF8", //
"#FADBD8", //
"#D5F5E3", // 绿
"#E8DAEF" //
]
private var isFormValid: Bool {
!name.isEmpty && selectedSemester != nil
}
var body: some View {
NavigationStack {
courseFormContent
.navigationTitle(navigationTitle)
.toolbarRole(.editor)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("取消") {
dismiss()
}
}
ToolbarItem(placement: .confirmationAction) {
Button("保存") {
saveCourse()
dismiss()
}
.disabled(!isFormValid)
}
}
.onAppear {
setupInitialValues()
}
}
}
// Main content view
private var courseFormContent: some View {
ZStack {
// Background
(colorScheme == .dark ? Color.black : Color.gray.opacity(0.1))
.ignoresSafeArea()
// Main content with horizontal layout
HStack(spacing: 0) {
// Left side - Form content
formFieldsSection
}
.frame(minWidth: 600, idealWidth: 700, maxHeight: .infinity)
}
}
// Form fields section
private var formFieldsSection: some View {
ScrollView(.vertical) {
VStack(spacing: 24) {
//
FormSection(title: "课程信息") {
courseInfoSection
}
//
FormSection(title: "课程颜色") {
colorSelectionSection
}
}
.padding()
.frame(maxWidth: .infinity)
}
.frame(minWidth: 300, idealWidth: 350, maxWidth: .infinity)
}
// Course information section
private var courseInfoSection: some View {
VStack(alignment: .leading, spacing: 16) {
// Course name input
VStack(alignment: .leading, spacing: 8) {
TextField("课程名称", text: $name)
.textFieldStyle(RoundedBorderTextFieldStyle())
.autocorrectionDisabled()
.frame(maxWidth: .infinity)
Text("请输入课程的完整名称")
.font(.caption)
.foregroundStyle(.secondary)
}
Divider()
//
HStack {
Text("学期:")
.foregroundStyle(.secondary)
Spacer()
Picker("学期", selection: $selectedSemester) {
if semesters.isEmpty {
Text("请先创建学期").tag(nil as Semester?)
} else {
ForEach(semesters) { semester in
Text(semester.title).tag(semester as Semester?)
}
}
}
.pickerStyle(.menu)
}
Divider()
}
.frame(maxWidth: .infinity)
}
// Color selection section
private var colorSelectionSection: some View {
VStack(alignment: .leading, spacing: 8) {
HStack {
ForEach(colorOptions, id: \.self) { colorHex in
Circle()
.fill(Color(hex: colorHex) ?? .gray)
.frame(width: 30, height: 30)
.overlay(
Circle()
.stroke(selectedColor == colorHex ? Color.black : Color.clear, lineWidth: 2)
)
.onTapGesture {
selectedColor = colorHex
}
.padding(5)
}
}
Text("选择一个颜色来标识课程")
.font(.caption)
.foregroundStyle(.secondary)
}
.frame(maxWidth: .infinity)
}
private var navigationTitle: String {
switch mode {
case .add:
return "添加课程"
case .edit:
return "编辑课程"
}
}
private func setupInitialValues() {
switch mode {
case .add:
//
selectedSemester = semesters.first(where: { $0.isCurrent }) ?? semesters.first
case .edit(let course):
name = course.name
selectedColor = course.colorHex
selectedSemester = course.semester
}
}
private func saveCourse() {
switch mode {
case .add:
guard let semester = selectedSemester else { return }
let newCourse = CourseModel(
name: name,
colorHex: selectedColor
)
newCourse.semester = semester
onSave(newCourse)
case .edit(let course):
course.name = name
course.colorHex = selectedColor
course.semester = selectedSemester
onSave(course)
}
}
}
#Preview("添加课程") {
CourseFormView(mode: .add, onSave: { _ in })
.modelContainer(PreviewData.createContainer())
}
#Preview("编辑课程") {
CourseFormView(mode: .edit(PreviewData.createSampleCourse()), onSave: { _ in })
.modelContainer(PreviewData.createContainer())
}