Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
Zyronon
2025-10-08 16:44:04 +08:00
11 changed files with 133 additions and 8 deletions

1
components.d.ts vendored
View File

@@ -44,6 +44,7 @@ declare module 'vue' {
IconFluentArrowLeft16Regular: typeof import('~icons/fluent/arrow-left16-regular')['default']
IconFluentArrowMove20Regular: typeof import('~icons/fluent/arrow-move20-regular')['default']
IconFluentArrowRight16Regular: typeof import('~icons/fluent/arrow-right16-regular')['default']
IconFluentArrowShuffle16Regular: typeof import('~icons/fluent/arrow-shuffle16-regular')['default']
IconFluentArrowSort20Regular: typeof import('~icons/fluent/arrow-sort20-regular')['default']
IconFluentBookLetter20Regular: typeof import('~icons/fluent/book-letter20-regular')['default']
IconFluentCheckmark20Regular: typeof import('~icons/fluent/checkmark20-regular')['default']

View File

@@ -119,9 +119,14 @@ const handlePause = () => {
isPlaying.value = false;
};
const emit = defineEmits<{
ended: []
}>();
const handleEnded = () => {
isPlaying.value = false;
currentTime.value = 0;
emit('ended');
};
const handleError = () => {

View File

@@ -47,7 +47,8 @@ defineExpose({scrollToBottom, scrollToItem})
<slot name="prefix" :item="item" :index="index"></slot>
</template>
<template v-slot="{ item, index }">
<div class="item-title">
<div class="item-title word-title">
<span class="index">{{ index + 1 }}.</span>
<span class="word" :class="!showWord && 'word-shadow'">{{ item.word }}</span>
<span class="phonetic" :class="!showWord && 'word-shadow'">{{ item.phonetic0 }}</span>
<VolumeIcon class="volume" @click="playWordAudio(item.word)"></VolumeIcon>
@@ -72,5 +73,10 @@ defineExpose({scrollToBottom, scrollToItem})
</template>
<style scoped lang="scss">
.word-title{
display: flex;
span{
flex-shrink: 0;
}
}
</style>

View File

@@ -33,6 +33,16 @@ let studyLoading = $ref(false)
let selectArticle: Article = $ref(getDefaultArticle())
// 计算当前选中文章的索引
const currentArticleIndex = computed(() => {
return runtimeStore.editDict.articles.findIndex(article => article.id === selectArticle.id)
})
// 处理播放下一个音频
const handlePlayNext = (nextArticle: Article) => {
selectArticle = nextArticle
}
function handleCheckedChange(val) {
selectArticle = val.item
}
@@ -191,7 +201,11 @@ const totalSpend = $computed(() => {
</div>
<div class="en-article-family title text-xl">
<div class="text-center text-2xl my-2">
<ArticleAudio :article="selectArticle"></ArticleAudio>
<ArticleAudio
:article="selectArticle"
:article-list="runtimeStore.editDict.articles"
:current-index="currentArticleIndex"
@play-next="handlePlayNext"></ArticleAudio>
</div>
<div class="text-center text-2xl">{{ selectArticle.title }}</div>
<div class="text-2xl" v-if="selectArticle.text">

View File

@@ -328,6 +328,14 @@ function changeArticle(val: ArticleItem) {
}
}
const handlePlayNext = (nextArticle: Article) => {
let rIndex = articleData.list.findIndex(v => v.id === nextArticle.id)
if (rIndex > -1) {
store.sbook.lastLearnIndex = rIndex
getCurrentPractice()
}
}
const {
isArticleCollect,
toggleArticleCollect
@@ -489,7 +497,12 @@ provide('currentPractice', currentPractice)
<div class="name">单词总数</div>
</div>
</div>
<ArticleAudio ref="audioRef" :article="articleData.article"></ArticleAudio>
<ArticleAudio
ref="audioRef"
:article="articleData.article"
:article-list="articleData.list"
:current-index="store.sbook.lastLearnIndex"
@play-next="handlePlayNext"></ArticleAudio>
<div class="flex flex-col items-center justify-center gap-1">
<div class="flex gap-2 center">
<Tooltip title="自动发音">

View File

@@ -7,10 +7,45 @@ import Audio from "@/components/base/Audio.vue";
const props = defineProps<{
article: Article
articleList?: Article[]
currentIndex?: number
}>()
let file = $ref(null)
let instance = $ref<{ audioRef: HTMLAudioElement }>({audioRef: null})
let shouldAutoPlay = $ref(false) // 标记是否应该自动播放
const emit = defineEmits<{
playNext: [nextArticle: Article]
}>()
// 处理音频播放结束,自动播放下一个
const handleAudioEnded = () => {
if (props.articleList && props.currentIndex !== undefined) {
const nextIndex = props.currentIndex + 1
if (nextIndex < props.articleList.length) {
const nextArticle = props.articleList[nextIndex]
if (nextArticle.audioSrc || nextArticle.audioFileId) {
shouldAutoPlay = true // 设置自动播放标记
emit('playNext', nextArticle)
}
}
}
}
// 当音频源改变时,如果需要自动播放则开始播放
const startAutoPlay = async () => {
if (shouldAutoPlay && instance?.audioRef) {
shouldAutoPlay = false // 重置标记
try {
// 等待一小段时间确保音频元素已经准备好
await new Promise(resolve => setTimeout(resolve, 100))
await instance.audioRef.play()
} catch (error) {
console.error('自动播放失败:', error)
}
}
}
watch(() => props.article.audioFileId, async () => {
if (!props.article.audioSrc && props.article.audioFileId) {
@@ -19,6 +54,9 @@ watch(() => props.article.audioFileId, async () => {
let rItem = list.find((file) => file.id === props.article.audioFileId)
if (rItem) {
file = URL.createObjectURL(rItem.file)
// 当文件加载完成后尝试自动播放
await new Promise(resolve => setTimeout(resolve, 50))
startAutoPlay()
}
}
}else {
@@ -26,6 +64,15 @@ watch(() => props.article.audioFileId, async () => {
}
}, {immediate: true})
// 监听音频源变化,触发自动播放
watch(() => props.article.audioSrc, async (newSrc) => {
if (newSrc) {
// 当音频源改变后尝试自动播放
await new Promise(resolve => setTimeout(resolve, 50))
startAutoPlay()
}
})
//转发一遍这里Proxy的默认值不能为{}可能是vue做了什么
defineExpose(new Proxy({
currentTime: 0,
@@ -57,8 +104,10 @@ defineExpose(new Proxy({
<template>
<Audio v-bind="$attrs" ref="instance"
v-if="props.article.audioSrc"
:src="props.article.audioSrc"/>
:src="props.article.audioSrc"
@ended="handleAudioEnded"/>
<Audio ref="instance" v-else-if="file"
:src="file"
@ended="handleAudioEnded"
/>
</template>

View File

@@ -152,7 +152,9 @@ function getShortcutKeyName(key: string): string {
'ToggleDictation': '切换默写模式',
'ToggleTheme': '切换主题',
'ToggleConciseMode': '切换简洁模式',
'TogglePanel': '切换面板'
'TogglePanel': '切换面板',
'RandomWrite': '随机默写',
'NextRandomWrite': '继续随机默写'
}
return shortcutKeyNameMap[key] || key

View File

@@ -57,6 +57,8 @@ let data = $ref<PracticeData>({
wrongWords: [],
})
let isRandomWrite = false;
async function loadDict() {
// console.log('load好了开始加载')
let dict = getDefaultDict()
@@ -229,7 +231,8 @@ function next(isTyping: boolean = true) {
//开始默写新词
if (statStore.step === 0) {
if (settingStore.wordPracticeMode === 1) {
if (settingStore.wordPracticeMode === 1 || isRandomWrite) {
isRandomWrite = false
console.log('自由模式,全完学完了')
showStatDialog = true
localStorage.removeItem(PracticeSaveWordKey.key)
@@ -380,9 +383,24 @@ function continueStudy() {
initData(getCurrentStudyWord())
}
function randomWrite() {
console.log('随机默写')
data.words = shuffle(data.words);
data.index = 0
settingStore.dictation = true
isRandomWrite = true
}
function nextRandomWrite() {
console.log('继续随机默写')
initData(getCurrentStudyWord())
randomWrite();
showStatDialog = false
}
useEvents([
[EventKey.repeatStudy, repeat],
[EventKey.continueStudy, continueStudy],
[EventKey.randomWrite, nextRandomWrite],
[EventKey.changeDict, () => {
initData(getCurrentStudyWord())
}],
@@ -401,6 +419,8 @@ useEvents([
[ShortcutKey.ToggleTheme, toggleTheme],
[ShortcutKey.ToggleConciseMode, toggleConciseMode],
[ShortcutKey.TogglePanel, togglePanel],
[ShortcutKey.RandomWrite, randomWrite],
[ShortcutKey.NextRandomWrite, nextRandomWrite],
])
</script>
@@ -453,6 +473,11 @@ useEvents([
:title="`下一组(${settingStore.shortcutKeyMap[ShortcutKey.NextChapter]})`">
<IconFluentArrowRight16Regular class="arrow" width="22"/>
</BaseIcon>
<BaseIcon
@click="randomWrite"
:title="`随机默写(${settingStore.shortcutKeyMap[ShortcutKey.RandomWrite]})`">
<IconFluentArrowShuffle16Regular class="arrow" width="22"/>
</BaseIcon>
</div>
</template>
<div class="panel-page-item pl-4">

View File

@@ -168,6 +168,11 @@ function options(emitType: string) {
@click="options(EventKey.continueStudy)">
{{ dictIsEnd ? '重新练习' : '再来一组' }}
</BaseButton>
<BaseButton
:keyboard="settingStore.shortcutKeyMap[ShortcutKey.NextRandomWrite]"
@click="options(EventKey.randomWrite)">
继续默写
</BaseButton>
<BaseButton @click="$router.back">
返回主页
</BaseButton>

View File

@@ -115,7 +115,9 @@ export enum ShortcutKey {
ToggleDictation = 'ToggleDictation',
ToggleTheme = 'ToggleTheme',
ToggleConciseMode = 'ToggleConciseMode',
TogglePanel = 'TogglePanel'
TogglePanel = 'TogglePanel',
RandomWrite = 'RandomWrite',
NextRandomWrite = 'NextRandomWrite'
}
export const DefaultShortcutKeyMap = {
@@ -135,6 +137,8 @@ export const DefaultShortcutKeyMap = {
[ShortcutKey.ToggleTheme]: 'Ctrl+Q',
[ShortcutKey.ToggleConciseMode]: 'Ctrl+M',
[ShortcutKey.TogglePanel]: 'Ctrl+L',
[ShortcutKey.RandomWrite]: 'Ctrl+R',
[ShortcutKey.NextRandomWrite]: 'Ctrl+Shift+R',
}
export enum TranslateEngine {

View File

@@ -17,6 +17,7 @@ export const EventKey = {
editDict: 'editDict',
openMyDictDialog: 'openMyDictDialog',
stateInitEnd: 'stateInitEnd',
randomWrite: 'randomWrite',
}
export function useEvent(key: string, func: any) {