新增拼写功能:出题随机空缺字母,用户输入完整单词

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-07-18 21:24:57 +08:00
parent 1d7b48837c
commit 94ef6ad84a
3 changed files with 95 additions and 27 deletions
+23 -3
View File
@@ -3,6 +3,7 @@ import Foundation
enum QuizMode: String, Hashable { enum QuizMode: String, Hashable {
case enToZh = "英选汉" case enToZh = "英选汉"
case zhToEn = "汉选英" case zhToEn = "汉选英"
case spelling = "拼写"
} }
struct QuizSetupRequest: Hashable { struct QuizSetupRequest: Hashable {
@@ -21,6 +22,7 @@ struct QuizQuestion: Identifiable {
let prompt: String let prompt: String
let options: [String] let options: [String]
let answer: String let answer: String
let blankedWord: String
} }
enum QuizGenerator { enum QuizGenerator {
@@ -32,16 +34,34 @@ enum QuizGenerator {
word: word, word: word,
prompt: promptText(word, mode), prompt: promptText(word, mode),
options: options, options: options,
answer: optionText(word, mode) answer: optionText(word, mode),
blankedWord: mode == .spelling ? blankWord(word.word) : ""
) )
} }
} }
private static func promptText(_ word: Word, _ mode: QuizMode) -> String { private static func promptText(_ word: Word, _ mode: QuizMode) -> String {
mode == .enToZh ? word.word : word.meaning switch mode {
case .enToZh: return word.word
case .zhToEn: return word.meaning
case .spelling: return word.meaning
}
} }
private static func optionText(_ word: Word, _ mode: QuizMode) -> String { private static func optionText(_ word: Word, _ mode: QuizMode) -> String {
mode == .enToZh ? word.meaning : word.word switch mode {
case .enToZh: return word.meaning
case .zhToEn: return word.word
case .spelling: return word.word
}
}
private static func blankWord(_ word: String) -> String {
let chars = Array(word)
let count = chars.count
guard count > 2 else { return String(repeating: "_", count: count) }
let blanks = max(1, Int(Double(count) * 0.4))
let indices = Set(Array(0..<count).shuffled().prefix(blanks))
return chars.enumerated().map { indices.contains($0.offset) ? "_" : String($0.element) }.joined()
} }
} }
+63 -16
View File
@@ -9,6 +9,7 @@ struct QuizView: View {
@State private var questions: [QuizQuestion] @State private var questions: [QuizQuestion]
@State private var currentIndex = 0 @State private var currentIndex = 0
@State private var selectedOption: String? @State private var selectedOption: String?
@State private var userInput = ""
@State private var wrongWords: [Word] = [] @State private var wrongWords: [Word] = []
@State private var finished = false @State private var finished = false
@State private var startDate = Date.now @State private var startDate = Date.now
@@ -67,6 +68,44 @@ struct QuizView: View {
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.padding(.vertical, 24) .padding(.vertical, 24)
if config.mode == .spelling {
spellingView(question)
} else {
optionsView(question)
}
if let selectedOption {
VStack(spacing: 12) {
if config.mode == .spelling {
Text(selectedOption == question.answer ? "正确" : "正确答案:\(question.answer)")
.font(.title2.weight(.bold))
.foregroundStyle(selectedOption == question.answer ? .green : .red)
Text(question.word.phonetic)
.font(.subheadline)
.foregroundStyle(.secondary)
} else {
Text(selectedOption == question.answer ? "回答正确" : "正确答案:\(question.answer)")
.font(.headline)
.foregroundStyle(selectedOption == question.answer ? .green : .red)
}
Button {
next()
} label: {
Text(currentIndex + 1 < questions.count ? "下一题" : "查看成绩")
.frame(maxWidth: .infinity)
.frame(height: 44)
}
.buttonStyle(.borderedProminent)
.font(.headline)
}
}
Spacer()
}
.padding()
}
private func optionsView(_ question: QuizQuestion) -> some View {
VStack(spacing: 12) { VStack(spacing: 12) {
ForEach(question.options, id: \.self) { option in ForEach(question.options, id: \.self) { option in
Button { Button {
@@ -89,27 +128,33 @@ struct QuizView: View {
.disabled(selectedOption != nil) .disabled(selectedOption != nil)
} }
} }
}
if let selectedOption { private func spellingView(_ question: QuizQuestion) -> some View {
VStack(spacing: 12) { VStack(spacing: 16) {
Text(selectedOption == question.answer ? "回答正确" : "正确答案:\(question.answer)") Text(question.blankedWord)
.font(.headline) .font(.system(size: 44, weight: .bold, design: .monospaced))
.foregroundStyle(selectedOption == question.answer ? .green : .red) .tracking(6)
Button { Text("\(question.word.word.count) 个字母")
next() .font(.subheadline)
} label: { .foregroundStyle(.secondary)
Text(currentIndex + 1 < questions.count ? "下一题" : "查看成绩") HStack(spacing: 12) {
.frame(maxWidth: .infinity) TextField("输入完整单词", text: $userInput)
.frame(height: 44) .textFieldStyle(.roundedBorder)
.autocapitalization(.none)
.disableAutocorrection(true)
.disabled(selectedOption != nil)
Button("确认") {
guard !userInput.trimmingCharacters(in: .whitespaces).isEmpty else { return }
selectedOption = userInput.trimmingCharacters(in: .whitespaces).lowercased()
if selectedOption != question.answer {
wrongWords.append(question.word)
}
} }
.buttonStyle(.borderedProminent) .buttonStyle(.borderedProminent)
.font(.headline) .disabled(userInput.trimmingCharacters(in: .whitespaces).isEmpty || selectedOption != nil)
} }
} }
Spacer()
}
.padding()
} }
private func optionColor(_ option: String, for question: QuizQuestion) -> Color { private func optionColor(_ option: String, for question: QuizQuestion) -> Color {
@@ -131,6 +176,7 @@ struct QuizView: View {
if currentIndex + 1 < questions.count { if currentIndex + 1 < questions.count {
currentIndex += 1 currentIndex += 1
selectedOption = nil selectedOption = nil
userInput = ""
} else { } else {
finished = true finished = true
} }
@@ -140,6 +186,7 @@ struct QuizView: View {
questions = QuizGenerator.makeQuestions(mode: config.mode, words: config.words) questions = QuizGenerator.makeQuestions(mode: config.mode, words: config.words)
currentIndex = 0 currentIndex = 0
selectedOption = nil selectedOption = nil
userInput = ""
wrongWords = [] wrongWords = []
finished = false finished = false
startDate = .now startDate = .now
@@ -25,6 +25,7 @@ struct WordListView: View {
Menu { Menu {
Button("英选汉") { startQuiz(.enToZh) } Button("英选汉") { startQuiz(.enToZh) }
Button("汉选英") { startQuiz(.zhToEn) } Button("汉选英") { startQuiz(.zhToEn) }
Button("拼写") { startQuiz(.spelling) }
} label: { } label: {
Text("测验") Text("测验")
} }