饮食分类支持子分类分组,WordCategory 新增 subcategories 字段实现单词分层展示
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,12 @@ struct WordCategory: Codable, Identifiable, Hashable {
|
|||||||
let id: String
|
let id: String
|
||||||
let name: String
|
let name: String
|
||||||
let icon: String
|
let icon: String
|
||||||
|
var words: [Word]
|
||||||
|
var subcategories: [Subcategory]?
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Subcategory: Codable, Hashable {
|
||||||
|
let name: String
|
||||||
let words: [Word]
|
let words: [Word]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -290,43 +290,82 @@
|
|||||||
"id": "food_drink",
|
"id": "food_drink",
|
||||||
"name": "饮食",
|
"name": "饮食",
|
||||||
"icon": "fork.knife",
|
"icon": "fork.knife",
|
||||||
|
"words": [],
|
||||||
|
"subcategories": [
|
||||||
|
{
|
||||||
|
"name": "主食",
|
||||||
"words": [
|
"words": [
|
||||||
{ "word": "rice", "phonetic": "/raɪs/", "meaning": "米饭", "example": "I eat rice every day.", "exampleMeaning": "我每天吃米饭。" },
|
{ "word": "rice", "phonetic": "/raɪs/", "meaning": "米饭", "example": "I eat rice every day.", "exampleMeaning": "我每天吃米饭。" },
|
||||||
{ "word": "bread", "phonetic": "/bred/", "meaning": "面包", "example": "I have bread for breakfast.", "exampleMeaning": "我早餐吃面包。" },
|
{ "word": "bread", "phonetic": "/bred/", "meaning": "面包", "example": "I have bread for breakfast.", "exampleMeaning": "我早餐吃面包。" },
|
||||||
{ "word": "noodle", "phonetic": "/ˈnuːdl/", "meaning": "面条", "example": "I like beef noodles.", "exampleMeaning": "我喜欢牛肉面。" },
|
{ "word": "noodle", "phonetic": "/ˈnuːdl/", "meaning": "面条", "example": "I like beef noodles.", "exampleMeaning": "我喜欢牛肉面。" },
|
||||||
{ "word": "dumpling", "phonetic": "/ˈdʌmplɪŋ/", "meaning": "饺子", "example": "We make dumplings together.", "exampleMeaning": "我们一起包饺子。" },
|
{ "word": "dumpling", "phonetic": "/ˈdʌmplɪŋ/", "meaning": "饺子", "example": "We make dumplings together.", "exampleMeaning": "我们一起包饺子。" }
|
||||||
{ "word": "egg", "phonetic": "/eɡ/", "meaning": "鸡蛋", "example": "I have an egg for breakfast.", "exampleMeaning": "我早餐吃一个鸡蛋。" },
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "肉类",
|
||||||
|
"words": [
|
||||||
{ "word": "meat", "phonetic": "/miːt/", "meaning": "肉", "example": "I like to eat meat.", "exampleMeaning": "我喜欢吃肉。" },
|
{ "word": "meat", "phonetic": "/miːt/", "meaning": "肉", "example": "I like to eat meat.", "exampleMeaning": "我喜欢吃肉。" },
|
||||||
{ "word": "beef", "phonetic": "/biːf/", "meaning": "牛肉", "example": "The beef is tender.", "exampleMeaning": "牛肉很嫩。" },
|
{ "word": "beef", "phonetic": "/biːf/", "meaning": "牛肉", "example": "The beef is tender.", "exampleMeaning": "牛肉很嫩。" },
|
||||||
{ "word": "pork", "phonetic": "/pɔːrk/", "meaning": "猪肉", "example": "I don't eat pork.", "exampleMeaning": "我不吃猪肉。" },
|
{ "word": "pork", "phonetic": "/pɔːrk/", "meaning": "猪肉", "example": "I don't eat pork.", "exampleMeaning": "我不吃猪肉。" },
|
||||||
{ "word": "chicken", "phonetic": "/ˈtʃɪkɪn/", "meaning": "鸡肉", "example": "Fried chicken is popular.", "exampleMeaning": "炸鸡很受欢迎。" },
|
{ "word": "chicken", "phonetic": "/ˈtʃɪkɪn/", "meaning": "鸡肉", "example": "Fried chicken is popular.", "exampleMeaning": "炸鸡很受欢迎。" },
|
||||||
|
{ "word": "egg", "phonetic": "/eɡ/", "meaning": "鸡蛋", "example": "I have an egg for breakfast.", "exampleMeaning": "我早餐吃一个鸡蛋。" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "蔬菜",
|
||||||
|
"words": [
|
||||||
{ "word": "vegetable", "phonetic": "/ˈvedʒtəbl/", "meaning": "蔬菜", "example": "Eat more vegetables.", "exampleMeaning": "多吃蔬菜。" },
|
{ "word": "vegetable", "phonetic": "/ˈvedʒtəbl/", "meaning": "蔬菜", "example": "Eat more vegetables.", "exampleMeaning": "多吃蔬菜。" },
|
||||||
{ "word": "carrot", "phonetic": "/ˈkærət/", "meaning": "胡萝卜", "example": "Rabbits like carrots.", "exampleMeaning": "兔子喜欢胡萝卜。" },
|
{ "word": "carrot", "phonetic": "/ˈkærət/", "meaning": "胡萝卜", "example": "Rabbits like carrots.", "exampleMeaning": "兔子喜欢胡萝卜。" },
|
||||||
{ "word": "tomato", "phonetic": "/təˈmeɪtoʊ/", "meaning": "番茄", "example": "The tomato is red.", "exampleMeaning": "番茄是红色的。" },
|
{ "word": "tomato", "phonetic": "/təˈmeɪtoʊ/", "meaning": "番茄", "example": "The tomato is red.", "exampleMeaning": "番茄是红色的。" },
|
||||||
{ "word": "potato", "phonetic": "/pəˈteɪtoʊ/", "meaning": "土豆", "example": "Potatoes grow underground.", "exampleMeaning": "土豆长在地下。" },
|
{ "word": "potato", "phonetic": "/pəˈteɪtoʊ/", "meaning": "土豆", "example": "Potatoes grow underground.", "exampleMeaning": "土豆长在地下。" },
|
||||||
{ "word": "onion", "phonetic": "/ˈʌnjən/", "meaning": "洋葱", "example": "The onion makes me cry.", "exampleMeaning": "洋葱让我流泪。" },
|
{ "word": "onion", "phonetic": "/ˈʌnjən/", "meaning": "洋葱", "example": "The onion makes me cry.", "exampleMeaning": "洋葱让我流泪。" },
|
||||||
{ "word": "cucumber", "phonetic": "/ˈkjuːkʌmbər/", "meaning": "黄瓜", "example": "The cucumber is fresh.", "exampleMeaning": "黄瓜很新鲜。" },
|
{ "word": "cucumber", "phonetic": "/ˈkjuːkʌmbər/", "meaning": "黄瓜", "example": "The cucumber is fresh.", "exampleMeaning": "黄瓜很新鲜。" },
|
||||||
{ "word": "cabbage", "phonetic": "/ˈkæbɪdʒ/", "meaning": "卷心菜", "example": "Cabbage is good for health.", "exampleMeaning": "卷心菜对健康有好处。" },
|
{ "word": "cabbage", "phonetic": "/ˈkæbɪdʒ/", "meaning": "卷心菜", "example": "Cabbage is good for health.", "exampleMeaning": "卷心菜对健康有好处。" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "饮品",
|
||||||
|
"words": [
|
||||||
{ "word": "water", "phonetic": "/ˈwɔːtər/", "meaning": "水", "example": "I drink water every day.", "exampleMeaning": "我每天喝水。" },
|
{ "word": "water", "phonetic": "/ˈwɔːtər/", "meaning": "水", "example": "I drink water every day.", "exampleMeaning": "我每天喝水。" },
|
||||||
{ "word": "milk", "phonetic": "/mɪlk/", "meaning": "牛奶", "example": "I drink a glass of milk.", "exampleMeaning": "我喝一杯牛奶。" },
|
{ "word": "milk", "phonetic": "/mɪlk/", "meaning": "牛奶", "example": "I drink a glass of milk.", "exampleMeaning": "我喝一杯牛奶。" },
|
||||||
{ "word": "juice", "phonetic": "/dʒuːs/", "meaning": "果汁", "example": "Orange juice is sweet.", "exampleMeaning": "橙汁很甜。" },
|
{ "word": "juice", "phonetic": "/dʒuːs/", "meaning": "果汁", "example": "Orange juice is sweet.", "exampleMeaning": "橙汁很甜。" },
|
||||||
{ "word": "tea", "phonetic": "/tiː/", "meaning": "茶", "example": "I drink tea in the afternoon.", "exampleMeaning": "我下午喝茶。" },
|
{ "word": "tea", "phonetic": "/tiː/", "meaning": "茶", "example": "I drink tea in the afternoon.", "exampleMeaning": "我下午喝茶。" },
|
||||||
{ "word": "coffee", "phonetic": "/ˈkɔːfi/", "meaning": "咖啡", "example": "I need a cup of coffee.", "exampleMeaning": "我需要一杯咖啡。" },
|
{ "word": "coffee", "phonetic": "/ˈkɔːfi/", "meaning": "咖啡", "example": "I need a cup of coffee.", "exampleMeaning": "我需要一杯咖啡。" },
|
||||||
{ "word": "beer", "phonetic": "/bɪr/", "meaning": "啤酒", "example": "He drinks beer with friends.", "exampleMeaning": "他和朋友喝啤酒。" },
|
{ "word": "beer", "phonetic": "/bɪr/", "meaning": "啤酒", "example": "He drinks beer with friends.", "exampleMeaning": "他和朋友喝啤酒。" },
|
||||||
{ "word": "wine", "phonetic": "/waɪn/", "meaning": "葡萄酒", "example": "Red wine is good for health.", "exampleMeaning": "红酒对健康有好处。" },
|
{ "word": "wine", "phonetic": "/waɪn/", "meaning": "葡萄酒", "example": "Red wine is good for health.", "exampleMeaning": "红酒对健康有好处。" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "调料",
|
||||||
|
"words": [
|
||||||
{ "word": "salt", "phonetic": "/sɔːlt/", "meaning": "盐", "example": "Don't add too much salt.", "exampleMeaning": "不要加太多盐。" },
|
{ "word": "salt", "phonetic": "/sɔːlt/", "meaning": "盐", "example": "Don't add too much salt.", "exampleMeaning": "不要加太多盐。" },
|
||||||
{ "word": "sugar", "phonetic": "/ˈʃʊɡər/", "meaning": "糖", "example": "I put sugar in my coffee.", "exampleMeaning": "我在咖啡里加糖。" },
|
{ "word": "sugar", "phonetic": "/ˈʃʊɡər/", "meaning": "糖", "example": "I put sugar in my coffee.", "exampleMeaning": "我在咖啡里加糖。" },
|
||||||
{ "word": "oil", "phonetic": "/ɔɪl/", "meaning": "油", "example": "Add some oil to the pan.", "exampleMeaning": "往锅里加点油。" },
|
{ "word": "oil", "phonetic": "/ɔɪl/", "meaning": "油", "example": "Add some oil to the pan.", "exampleMeaning": "往锅里加点油。" },
|
||||||
{ "word": "sauce", "phonetic": "/sɔːs/", "meaning": "酱", "example": "The sauce is delicious.", "exampleMeaning": "这个酱很好吃。" },
|
{ "word": "sauce", "phonetic": "/sɔːs/", "meaning": "酱", "example": "The sauce is delicious.", "exampleMeaning": "这个酱很好吃。" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "餐食",
|
||||||
|
"words": [
|
||||||
{ "word": "breakfast", "phonetic": "/ˈbrekfəst/", "meaning": "早餐", "example": "Breakfast is the most important meal.", "exampleMeaning": "早餐是最重要的一餐。" },
|
{ "word": "breakfast", "phonetic": "/ˈbrekfəst/", "meaning": "早餐", "example": "Breakfast is the most important meal.", "exampleMeaning": "早餐是最重要的一餐。" },
|
||||||
{ "word": "lunch", "phonetic": "/lʌntʃ/", "meaning": "午餐", "example": "I have lunch at twelve.", "exampleMeaning": "我十二点吃午餐。" },
|
{ "word": "lunch", "phonetic": "/lʌntʃ/", "meaning": "午餐", "example": "I have lunch at twelve.", "exampleMeaning": "我十二点吃午餐。" },
|
||||||
{ "word": "dinner", "phonetic": "/ˈdɪnər/", "meaning": "晚餐", "example": "We have dinner at six.", "exampleMeaning": "我们六点吃晚餐。" },
|
{ "word": "dinner", "phonetic": "/ˈdɪnər/", "meaning": "晚餐", "example": "We have dinner at six.", "exampleMeaning": "我们六点吃晚餐。" },
|
||||||
{ "word": "soup", "phonetic": "/suːp/", "meaning": "汤", "example": "The soup is hot.", "exampleMeaning": "汤是热的。" },
|
{ "word": "soup", "phonetic": "/suːp/", "meaning": "汤", "example": "The soup is hot.", "exampleMeaning": "汤是热的。" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "零食甜品",
|
||||||
|
"words": [
|
||||||
{ "word": "cake", "phonetic": "/keɪk/", "meaning": "蛋糕", "example": "I like chocolate cake.", "exampleMeaning": "我喜欢巧克力蛋糕。" },
|
{ "word": "cake", "phonetic": "/keɪk/", "meaning": "蛋糕", "example": "I like chocolate cake.", "exampleMeaning": "我喜欢巧克力蛋糕。" },
|
||||||
{ "word": "cookie", "phonetic": "/ˈkʊki/", "meaning": "饼干", "example": "The cookie is crunchy.", "exampleMeaning": "饼干很脆。" },
|
{ "word": "cookie", "phonetic": "/ˈkʊki/", "meaning": "饼干", "example": "The cookie is crunchy.", "exampleMeaning": "饼干很脆。" },
|
||||||
{ "word": "ice cream", "phonetic": "/ˌaɪs ˈkriːm/", "meaning": "冰淇淋", "example": "Ice cream is cold and sweet.", "exampleMeaning": "冰淇淋又冰又甜。" },
|
{ "word": "ice cream", "phonetic": "/ˌaɪs ˈkriːm/", "meaning": "冰淇淋", "example": "Ice cream is cold and sweet.", "exampleMeaning": "冰淇淋又冰又甜。" },
|
||||||
{ "word": "chocolate", "phonetic": "/ˈtʃɔːklət/", "meaning": "巧克力", "example": "I love dark chocolate.", "exampleMeaning": "我爱黑巧克力。" },
|
{ "word": "chocolate", "phonetic": "/ˈtʃɔːklət/", "meaning": "巧克力", "example": "I love dark chocolate.", "exampleMeaning": "我爱黑巧克力。" },
|
||||||
{ "word": "candy", "phonetic": "/ˈkændi/", "meaning": "糖果", "example": "The child likes candy.", "exampleMeaning": "小孩喜欢糖果。" },
|
{ "word": "candy", "phonetic": "/ˈkændi/", "meaning": "糖果", "example": "The child likes candy.", "exampleMeaning": "小孩喜欢糖果。" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "其他",
|
||||||
|
"words": [
|
||||||
{ "word": "sandwich", "phonetic": "/ˈsænwɪtʃ/", "meaning": "三明治", "example": "I made a sandwich for lunch.", "exampleMeaning": "我做了三明治当午餐。" },
|
{ "word": "sandwich", "phonetic": "/ˈsænwɪtʃ/", "meaning": "三明治", "example": "I made a sandwich for lunch.", "exampleMeaning": "我做了三明治当午餐。" },
|
||||||
{ "word": "pizza", "phonetic": "/ˈpiːtsə/", "meaning": "披萨", "example": "Pizza is from Italy.", "exampleMeaning": "披萨来自意大利。" },
|
{ "word": "pizza", "phonetic": "/ˈpiːtsə/", "meaning": "披萨", "example": "Pizza is from Italy.", "exampleMeaning": "披萨来自意大利。" },
|
||||||
{ "word": "hamburger", "phonetic": "/ˈhæmbɜːrɡər/", "meaning": "汉堡", "example": "The hamburger is big.", "exampleMeaning": "这个汉堡很大。" },
|
{ "word": "hamburger", "phonetic": "/ˈhæmbɜːrɡər/", "meaning": "汉堡", "example": "The hamburger is big.", "exampleMeaning": "这个汉堡很大。" },
|
||||||
@@ -335,4 +374,6 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,22 +28,29 @@ final class WordStore {
|
|||||||
let data = try Data(contentsOf: url)
|
let data = try Data(contentsOf: url)
|
||||||
let decoded = try JSONDecoder().decode(WordBank.self, from: data)
|
let decoded = try JSONDecoder().decode(WordBank.self, from: data)
|
||||||
let categories = decoded.categories.map { category -> WordCategory in
|
let categories = decoded.categories.map { category -> WordCategory in
|
||||||
|
var cat = category
|
||||||
|
let source: [Word]
|
||||||
|
if let subs = cat.subcategories, !subs.isEmpty {
|
||||||
|
source = subs.flatMap { $0.words }
|
||||||
|
} else {
|
||||||
|
source = cat.words
|
||||||
|
}
|
||||||
let tagged: [Word]
|
let tagged: [Word]
|
||||||
if category.id == "numbers" {
|
if category.id == "numbers" {
|
||||||
var words = NumberWords.makeWords(atoms: category.words)
|
var words = NumberWords.makeWords(atoms: source)
|
||||||
let extras = category.words.filter { NumberWords.atomNumbers[$0.word] == nil }
|
let extras = source.filter { NumberWords.atomNumbers[$0.word] == nil }
|
||||||
words.append(contentsOf: extras)
|
words.append(contentsOf: extras)
|
||||||
tagged = words
|
tagged = words
|
||||||
} else {
|
} else {
|
||||||
tagged = category.words
|
tagged = source
|
||||||
}
|
}
|
||||||
let words = tagged.map { word -> Word in
|
let words = tagged.map { word -> Word in
|
||||||
var w = word
|
var w = word
|
||||||
w.categoryId = category.id
|
w.categoryId = category.id
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
return WordCategory(id: category.id, name: category.name, icon: category.icon,
|
cat.words = words
|
||||||
words: words)
|
return cat
|
||||||
}
|
}
|
||||||
bank = WordBank(version: decoded.version, categories: categories)
|
bank = WordBank(version: decoded.version, categories: categories)
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -137,13 +137,33 @@ final class WordListViewController: UIViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension WordListViewController: UITableViewDataSource, UITableViewDelegate {
|
extension WordListViewController: UITableViewDataSource, UITableViewDelegate {
|
||||||
|
private var sections: [Subcategory]? {
|
||||||
|
category.subcategories.flatMap { $0.isEmpty ? nil : $0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
func numberOfSections(in tableView: UITableView) -> Int {
|
||||||
|
sections?.count ?? 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||||
|
sections?[section].name
|
||||||
|
}
|
||||||
|
|
||||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||||
category.words.count
|
if let sections {
|
||||||
|
return sections[section].words.count
|
||||||
|
}
|
||||||
|
return category.words.count
|
||||||
}
|
}
|
||||||
|
|
||||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! WordCardCell
|
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! WordCardCell
|
||||||
let word = category.words[indexPath.row]
|
let word: Word
|
||||||
|
if let sections {
|
||||||
|
word = sections[indexPath.section].words[indexPath.row]
|
||||||
|
} else {
|
||||||
|
word = category.words[indexPath.row]
|
||||||
|
}
|
||||||
cell.configure(with: word)
|
cell.configure(with: word)
|
||||||
cell.onSpeak = { word in
|
cell.onSpeak = { word in
|
||||||
SpeechService.shared.speak(word.word)
|
SpeechService.shared.speak(word.word)
|
||||||
@@ -158,7 +178,13 @@ extension WordListViewController: UITableViewDataSource, UITableViewDelegate {
|
|||||||
|
|
||||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
Haptics.selection()
|
Haptics.selection()
|
||||||
let detail = WordDetailViewController(word: category.words[indexPath.row], categoryName: category.name)
|
let word: Word
|
||||||
|
if let sections {
|
||||||
|
word = sections[indexPath.section].words[indexPath.row]
|
||||||
|
} else {
|
||||||
|
word = category.words[indexPath.row]
|
||||||
|
}
|
||||||
|
let detail = WordDetailViewController(word: word, categoryName: category.name)
|
||||||
navigationController?.pushViewController(detail, animated: true)
|
navigationController?.pushViewController(detail, animated: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user