From e5a245155a6de33de65cde1a7c1d1c7839d2b8d2 Mon Sep 17 00:00:00 2001
From: wsy182 <2392948297@qq.com>
Date: Thu, 26 Feb 2026 13:22:03 +0800
Subject: [PATCH] feat(word): show/play UK-US audio and add example sentence
---
memora-api/internal/model/model.go | 21 ++---
memora-api/internal/service/word.go | 122 ++++++++++++++++++++++-----
memora-api/sql/init.sql | 1 +
memora-web/src/services/api/types.ts | 1 +
memora-web/src/views/Memory.vue | 23 ++++-
5 files changed, 132 insertions(+), 36 deletions(-)
diff --git a/memora-api/internal/model/model.go b/memora-api/internal/model/model.go
index 790c71f..fe4695e 100644
--- a/memora-api/internal/model/model.go
+++ b/memora-api/internal/model/model.go
@@ -5,16 +5,17 @@ import (
)
type Word struct {
- ID int64 `json:"id" gorm:"primaryKey"`
- Word string `json:"word" gorm:"size:100;uniqueIndex;not null"`
- PhoneticUK string `json:"phonetic_uk" gorm:"size:255"`
- PhoneticUS string `json:"phonetic_us" gorm:"size:255"`
- AudioUK string `json:"audio_uk" gorm:"size:500"`
- AudioUS string `json:"audio_us" gorm:"size:500"`
- PartOfSpeech string `json:"part_of_speech" gorm:"size:50"`
- Definition string `json:"definition" gorm:"type:text"`
- CreatedAt time.Time `json:"created_at"`
- UpdatedAt time.Time `json:"updated_at"`
+ ID int64 `json:"id" gorm:"primaryKey"`
+ Word string `json:"word" gorm:"size:100;uniqueIndex;not null"`
+ PhoneticUK string `json:"phonetic_uk" gorm:"size:255"`
+ PhoneticUS string `json:"phonetic_us" gorm:"size:255"`
+ AudioUK string `json:"audio_uk" gorm:"size:500"`
+ AudioUS string `json:"audio_us" gorm:"size:500"`
+ PartOfSpeech string `json:"part_of_speech" gorm:"size:50"`
+ Definition string `json:"definition" gorm:"type:text"`
+ ExampleSentence string `json:"example_sentence" gorm:"type:text"`
+ CreatedAt time.Time `json:"created_at"`
+ UpdatedAt time.Time `json:"updated_at"`
}
func (Word) TableName() string {
diff --git a/memora-api/internal/service/word.go b/memora-api/internal/service/word.go
index 9a1925a..18a1129 100644
--- a/memora-api/internal/service/word.go
+++ b/memora-api/internal/service/word.go
@@ -7,6 +7,7 @@ import (
"fmt"
"io"
"net/http"
+ "net/url"
"os"
"regexp"
"strings"
@@ -26,6 +27,65 @@ func NewWordService(db *gorm.DB) *WordService {
return &WordService{db: db}
}
+type dictAPIEntry struct {
+ PhoneticUK string
+ PhoneticUS string
+ Part string
+ Example string
+}
+
+// fallback: dictionaryapi.dev(补充音标/词性/例句)
+func (s *WordService) fetchDictMeta(word string) *dictAPIEntry {
+ endpoint := "https://api.dictionaryapi.dev/api/v2/entries/en/" + url.PathEscape(strings.ToLower(word))
+ resp, err := http.Get(endpoint)
+ if err != nil || resp == nil {
+ return nil
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode != http.StatusOK {
+ return nil
+ }
+ body, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return nil
+ }
+ var arr []map[string]any
+ if err := json.Unmarshal(body, &arr); err != nil || len(arr) == 0 {
+ return nil
+ }
+ entry := &dictAPIEntry{}
+ first := arr[0]
+ if p, ok := first["phonetics"].([]any); ok {
+ for _, x := range p {
+ if m, ok := x.(map[string]any); ok {
+ if t, ok := m["text"].(string); ok && t != "" {
+ if entry.PhoneticUK == "" {
+ entry.PhoneticUK = t
+ }
+ if entry.PhoneticUS == "" {
+ entry.PhoneticUS = t
+ }
+ }
+ }
+ }
+ }
+ if ms, ok := first["meanings"].([]any); ok && len(ms) > 0 {
+ if m0, ok := ms[0].(map[string]any); ok {
+ if p, ok := m0["partOfSpeech"].(string); ok {
+ entry.Part = p
+ }
+ if defs, ok := m0["definitions"].([]any); ok && len(defs) > 0 {
+ if d0, ok := defs[0].(map[string]any); ok {
+ if ex, ok := d0["example"].(string); ok {
+ entry.Example = ex
+ }
+ }
+ }
+ }
+ }
+ return entry
+}
+
// 查询单词(调用有道API)
func (s *WordService) QueryWord(word string) (*model.YoudaoResponse, error) {
// 优先用 app_id,兼容 app_key
@@ -117,7 +177,7 @@ func (s *WordService) SaveWord(word string, youdaoResp *model.YoudaoResponse) (*
}
// 解析有道API响应
- var phoneticUK, phoneticUS, partOfSpeech, definition string
+ var phoneticUK, phoneticUS, partOfSpeech, definition, exampleSentence string
// 解析音标
if youdaoResp.Basic.UkPhonetic != "" {
@@ -156,36 +216,52 @@ func (s *WordService) SaveWord(word string, youdaoResp *model.YoudaoResponse) (*
partOfSpeech = "n./v./adj."
}
- // 音频 URL - 有道返回的 speakUrl 示例: https://dict.youdao.com/dictvoice?audio=hello
+ // 音频 URL:使用有道 dictvoice,type=1(英音) type=2(美音)
audioPath := config.AppConfig.Audio.Path
var audioUK, audioUS string
- if youdaoResp.SpeakUrl != "" {
- // 默认是美音,加 _uk 是英音
- audioUK = strings.Replace(youdaoResp.SpeakUrl, "audio=", "audio=word_uk_", 1)
- audioUS = youdaoResp.SpeakUrl
+ ukURL := fmt.Sprintf("https://dict.youdao.com/dictvoice?audio=%s&type=1", url.QueryEscape(word))
+ usURL := fmt.Sprintf("https://dict.youdao.com/dictvoice?audio=%s&type=2", url.QueryEscape(word))
+ if audioPath != "" {
+ ukPath := fmt.Sprintf("%s/%s_uk.mp3", audioPath, word)
+ usPath := fmt.Sprintf("%s/%s_us.mp3", audioPath, word)
+ go s.DownloadAudio(ukURL, ukPath)
+ go s.DownloadAudio(usURL, usPath)
+ audioUK = ukPath
+ audioUS = usPath
+ }
- // 异步下载音频文件
- go func() {
- if audioPath != "" {
- ukPath := fmt.Sprintf("%s/%s_uk.mp3", audioPath, word)
- usPath := fmt.Sprintf("%s/%s_us.mp3", audioPath, word)
- s.DownloadAudio(audioUK, ukPath)
- s.DownloadAudio(audioUS, usPath)
+ // 补充元信息(音标/词性/例句)
+ meta := s.fetchDictMeta(word)
+ if meta != nil {
+ if phoneticUK == "" && meta.PhoneticUK != "" {
+ phoneticUK = meta.PhoneticUK
+ }
+ if phoneticUS == "" && meta.PhoneticUS != "" {
+ phoneticUS = meta.PhoneticUS
+ }
+ if partOfSpeech == "" || partOfSpeech == "n./v./adj." {
+ if meta.Part != "" {
+ partOfSpeech = meta.Part
}
- }()
- audioUK = fmt.Sprintf("%s/%s_uk.mp3", audioPath, word)
- audioUS = fmt.Sprintf("%s/%s_us.mp3", audioPath, word)
+ }
+ if meta.Example != "" {
+ exampleSentence = meta.Example
+ }
+ }
+ if exampleSentence == "" {
+ exampleSentence = fmt.Sprintf("Example: %s", word)
}
// 创建新单词
newWord := model.Word{
- Word: word,
- PhoneticUK: phoneticUK,
- PhoneticUS: phoneticUS,
- AudioUK: audioUK,
- AudioUS: audioUS,
- PartOfSpeech: partOfSpeech,
- Definition: definition,
+ Word: word,
+ PhoneticUK: phoneticUK,
+ PhoneticUS: phoneticUS,
+ AudioUK: audioUK,
+ AudioUS: audioUS,
+ PartOfSpeech: partOfSpeech,
+ Definition: definition,
+ ExampleSentence: exampleSentence,
}
if err := s.db.Create(&newWord).Error; err != nil {
diff --git a/memora-api/sql/init.sql b/memora-api/sql/init.sql
index 414ca94..f91bb59 100644
--- a/memora-api/sql/init.sql
+++ b/memora-api/sql/init.sql
@@ -12,6 +12,7 @@ CREATE TABLE IF NOT EXISTS words (
audio_us VARCHAR(500) COMMENT '美式音频文件路径',
part_of_speech VARCHAR(50) COMMENT '词性',
definition TEXT COMMENT '标准释义',
+ example_sentence TEXT COMMENT '例句',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_word (word)
diff --git a/memora-web/src/services/api/types.ts b/memora-web/src/services/api/types.ts
index 37af4f7..f3610b5 100644
--- a/memora-web/src/services/api/types.ts
+++ b/memora-web/src/services/api/types.ts
@@ -7,6 +7,7 @@ export interface Word {
audio_us?: string
part_of_speech?: string
definition?: string
+ example_sentence?: string
}
export interface MemoryRecord {
diff --git a/memora-web/src/views/Memory.vue b/memora-web/src/views/Memory.vue
index b4befc0..e1f1b05 100644
--- a/memora-web/src/views/Memory.vue
+++ b/memora-web/src/views/Memory.vue
@@ -32,8 +32,13 @@
{{ saved.word }}
{{ saved.part_of_speech }}
{{ saved.definition }}
- {{ saved.phonetic_uk }}
- {{ saved.phonetic_us }}
+ {{ saved.phonetic_uk || '暂无' }}
+ {{ saved.phonetic_us || '暂无' }}
+ {{ saved.example_sentence || '暂无' }}
+
+ 播放英音
+ 播放美音
+
@@ -41,7 +46,7 @@