实现背单词首页功能:内置分类词库,支持分类浏览、单词详情和离线发音

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-07-17 23:20:30 +08:00
parent 1a1563cd11
commit f5dd7fa724
8 changed files with 349 additions and 13 deletions
@@ -0,0 +1,41 @@
import SwiftUI
struct CategoryListView: View {
let categories: [WordCategory]
var body: some View {
NavigationStack {
List(categories) { category in
NavigationLink(value: category) {
HStack(spacing: 16) {
Image(systemName: category.icon)
.font(.title2)
.foregroundStyle(.white)
.frame(width: 44, height: 44)
.background(.tint, in: RoundedRectangle(cornerRadius: 10))
VStack(alignment: .leading, spacing: 4) {
Text(category.name)
.font(.headline)
Text("\(category.words.count) 个单词")
.font(.subheadline)
.foregroundStyle(.secondary)
}
}
.padding(.vertical, 4)
}
}
.navigationTitle("单词分类")
.navigationDestination(for: WordCategory.self) { category in
WordListView(category: category)
}
}
}
}
#Preview {
CategoryListView(categories: [
WordCategory(id: "colors", name: "颜色", icon: "paintpalette.fill", words: [
Word(word: "red", phonetic: "/red/", meaning: "红色", example: "The apple is red.", exampleMeaning: "苹果是红色的。")
])
])
}
@@ -0,0 +1,81 @@
import SwiftUI
struct WordDetailView: View {
let word: Word
@Environment(SpeechService.self) private var speech
private var isSpeaking: Bool {
speech.speakingText == word.word
}
var body: some View {
ScrollView {
VStack(spacing: 32) {
VStack(spacing: 12) {
Text(word.word)
.font(.system(size: 48, weight: .bold))
Text(word.phonetic)
.font(.title3)
.foregroundStyle(.secondary)
Button {
speech.speak(word.word)
} label: {
Label(isSpeaking ? "朗读中" : "发音", systemImage: isSpeaking ? "speaker.wave.3.fill" : "speaker.wave.2.fill")
.font(.headline)
.padding(.horizontal, 24)
.padding(.vertical, 10)
}
.buttonStyle(.borderedProminent)
.disabled(isSpeaking)
}
VStack(alignment: .leading, spacing: 16) {
VStack(alignment: .leading, spacing: 6) {
Text("释义")
.font(.caption)
.foregroundStyle(.secondary)
Text(word.meaning)
.font(.title2)
}
Divider()
VStack(alignment: .leading, spacing: 6) {
Text("例句")
.font(.caption)
.foregroundStyle(.secondary)
HStack(alignment: .top) {
VStack(alignment: .leading, spacing: 4) {
Text(word.example)
.font(.body)
Text(word.exampleMeaning)
.font(.subheadline)
.foregroundStyle(.secondary)
}
Spacer()
Button {
speech.speak(word.example)
} label: {
Image(systemName: "speaker.wave.2")
}
.buttonStyle(.borderless)
}
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
.background(.quaternary.opacity(0.5), in: RoundedRectangle(cornerRadius: 16))
}
.padding()
}
.navigationTitle(word.word)
.navigationBarTitleDisplayMode(.inline)
}
}
#Preview {
NavigationStack {
WordDetailView(word: Word(word: "red", phonetic: "/red/", meaning: "红色", example: "The apple is red.", exampleMeaning: "苹果是红色的。"))
.environment(SpeechService())
}
}
@@ -0,0 +1,32 @@
import SwiftUI
struct WordListView: View {
let category: WordCategory
var body: some View {
List(category.words) { word in
NavigationLink(value: word) {
VStack(alignment: .leading, spacing: 4) {
Text(word.word)
.font(.headline)
Text(word.meaning)
.font(.subheadline)
.foregroundStyle(.secondary)
}
.padding(.vertical, 2)
}
}
.navigationTitle(category.name)
.navigationDestination(for: Word.self) { word in
WordDetailView(word: word)
}
}
}
#Preview {
NavigationStack {
WordListView(category: WordCategory(id: "colors", name: "颜色", icon: "paintpalette.fill", words: [
Word(word: "red", phonetic: "/red/", meaning: "红色", example: "The apple is red.", exampleMeaning: "苹果是红色的。")
]))
}
}