Merge branch 'master' into dev
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.png"/>
|
||||
|
||||
@@ -136,42 +136,43 @@ defineExpose({
|
||||
closeImportDialog
|
||||
})
|
||||
defineRender(
|
||||
() => {
|
||||
const d = (item) => <Checkbox
|
||||
modelValue={selectIds.includes(item.id)}
|
||||
onChange={() => toggleSelect(item)}
|
||||
size="large"/>
|
||||
() => {
|
||||
const d = (item) => <Checkbox
|
||||
modelValue={selectIds.includes(item.id)}
|
||||
onChange={() => toggleSelect(item)}
|
||||
size="large"/>
|
||||
|
||||
return (
|
||||
<div class="flex flex-col gap-3">
|
||||
{
|
||||
props.showToolbar && <div>
|
||||
return (
|
||||
<div class="flex flex-col gap-3">
|
||||
{
|
||||
showSearchInput ? (
|
||||
<div class="flex gap-4">
|
||||
<BaseInput
|
||||
clearable
|
||||
modelValue={searchKey}
|
||||
onUpdate:modelValue={debounce(e => searchKey = e)}
|
||||
class="flex-1">
|
||||
{{
|
||||
subfix: () => <IconFluentSearch24Regular
|
||||
class="text-lg text-gray"
|
||||
/>
|
||||
}}
|
||||
</BaseInput>
|
||||
<BaseButton onClick={() => (showSearchInput = false, searchKey = '')}>取消</BaseButton>
|
||||
</div>
|
||||
) : (
|
||||
<div class="flex justify-between">
|
||||
<div class="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
disabled={!currentList.length}
|
||||
onChange={() => toggleSelectAll()}
|
||||
modelValue={selectAll}
|
||||
size="large"/>
|
||||
<span>{selectIds.length} / {list.value.length}</span>
|
||||
</div>
|
||||
props.showToolbar && <div>
|
||||
{
|
||||
showSearchInput ? (
|
||||
<div class="flex gap-4">
|
||||
<BaseInput
|
||||
clearable
|
||||
modelValue={searchKey}
|
||||
onUpdate:modelValue={debounce(e => searchKey = e)}
|
||||
class="flex-1"
|
||||
autofocus>
|
||||
{{
|
||||
subfix: () => <IconFluentSearch24Regular
|
||||
class="text-lg text-gray"
|
||||
/>
|
||||
}}
|
||||
</BaseInput>
|
||||
<BaseButton onClick={() => (showSearchInput = false, searchKey = '')}>取消</BaseButton>
|
||||
</div>
|
||||
) : (
|
||||
<div class="flex justify-between">
|
||||
<div class="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
disabled={!currentList.length}
|
||||
onChange={() => toggleSelectAll()}
|
||||
modelValue={selectAll}
|
||||
size="large"/>
|
||||
<span>{selectIds.length} / {list.value.length}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 relative">
|
||||
{
|
||||
|
||||
@@ -1,33 +1,71 @@
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
|
||||
type Theme = "light" | "dark";
|
||||
// 获取系统主题
|
||||
function getSystemTheme(): Theme {
|
||||
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
return 'dark';
|
||||
} else if (window.matchMedia('(prefers-color-scheme: light)').matches) {
|
||||
return 'light';
|
||||
}
|
||||
return 'light'; // 默认浅色模式
|
||||
}
|
||||
|
||||
// 交换主题名称
|
||||
function swapTheme(theme: Theme): Theme {
|
||||
return theme === 'light' ? 'dark' : 'light'
|
||||
}
|
||||
|
||||
// 监听系统主题变化
|
||||
function listenToSystemThemeChange(call: (theme: Theme) => void) {
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
|
||||
if (e.matches) {
|
||||
// console.log('系统已切换到深色模式');
|
||||
call('dark');
|
||||
}
|
||||
});
|
||||
window.matchMedia('(prefers-color-scheme: light)').addEventListener('change', e => {
|
||||
if (e.matches) {
|
||||
// console.log('系统已切换到浅色模式');
|
||||
call('light');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default function useTheme() {
|
||||
const settingStore = useSettingStore()
|
||||
|
||||
// // 查询当前系统主题颜色
|
||||
// const match: MediaQueryList = window.matchMedia("(prefers-color-scheme: dark)")
|
||||
// // 监听系统主题变化
|
||||
// match.addEventListener('change', followSystem)
|
||||
//
|
||||
// function followSystem() {
|
||||
// document.documentElement.className = match.matches ? 'dark' : 'light'
|
||||
// }
|
||||
|
||||
// 开启监听系统主题变更,后期可以通过用户配置来决定是否开启
|
||||
listenToSystemThemeChange((theme: Theme) => {
|
||||
// 如果系统主题变更后和当前的主题一致,则不需要再重新切换
|
||||
if(settingStore.theme === theme){
|
||||
return;
|
||||
}
|
||||
|
||||
settingStore.theme = theme;
|
||||
setTheme(theme);
|
||||
})
|
||||
|
||||
function toggleTheme() {
|
||||
if (settingStore.theme === 'auto') {
|
||||
settingStore.theme = 'light'
|
||||
} else {
|
||||
settingStore.theme = settingStore.theme === 'light' ? 'dark' : 'light'
|
||||
}
|
||||
setTheme(settingStore.theme)
|
||||
// auto模式下,默认是使用系统主题,切换时应该使用当前系统主题为基础进行切换
|
||||
settingStore.theme = swapTheme(settingStore.theme === 'auto' ? getSystemTheme() : settingStore.theme as Theme);
|
||||
setTheme(settingStore.theme);
|
||||
}
|
||||
|
||||
function setTheme(val) {
|
||||
document.documentElement.className = val
|
||||
function setTheme(val:string) {
|
||||
// auto模式下,则通过查询系统主题来设置主题名称
|
||||
document.documentElement.className = val === 'auto' ? getSystemTheme() : val;
|
||||
}
|
||||
|
||||
// 获取当前具体的主题名称
|
||||
function getTheme():Theme{
|
||||
// auto模式下,则通过查询系统主题来获取当前具体的主题名称
|
||||
return settingStore.theme === 'auto' ? getSystemTheme() : settingStore.theme as Theme;
|
||||
}
|
||||
|
||||
return {
|
||||
toggleTheme,
|
||||
setTheme
|
||||
setTheme,
|
||||
getTheme
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import {useRuntimeStore} from "@/stores/runtime.ts";
|
||||
const settingStore = useSettingStore()
|
||||
const runtimeStore = useRuntimeStore()
|
||||
const router = useRouter()
|
||||
const {toggleTheme} = useTheme()
|
||||
const {toggleTheme,getTheme} = useTheme()
|
||||
|
||||
|
||||
</script>
|
||||
@@ -58,7 +58,7 @@ const {toggleTheme} = useTheme()
|
||||
:title="`切换主题(${settingStore.shortcutKeyMap[ShortcutKey.ToggleTheme]})`"
|
||||
@click="toggleTheme"
|
||||
>
|
||||
<IconFluentWeatherMoon16Regular v-if="settingStore.theme === 'light'"/>
|
||||
<IconFluentWeatherMoon16Regular v-if="getTheme() === 'light'"/>
|
||||
<IconFluentWeatherSunny16Regular v-else/>
|
||||
</BaseIcon>
|
||||
</div>
|
||||
|
||||
@@ -183,6 +183,9 @@ watch(() => settingStore.wordPracticeType, (n) => {
|
||||
switch (n) {
|
||||
case WordPracticeType.Spell:
|
||||
case WordPracticeType.Dictation:
|
||||
settingStore.dictation = true;
|
||||
settingStore.translate = true;
|
||||
break
|
||||
case WordPracticeType.Listen:
|
||||
settingStore.dictation = true;
|
||||
settingStore.translate = false;
|
||||
@@ -240,9 +243,19 @@ async function next(isTyping: boolean = true) {
|
||||
}
|
||||
if (settingStore.wordPracticeMode === WordPracticeMode.Free) {
|
||||
if (data.index === data.words.length - 1) {
|
||||
console.log('自由模式,全完学完了')
|
||||
showStatDialog = true
|
||||
localStorage.removeItem(PracticeSaveWordKey.key)
|
||||
data.wrongWords = data.wrongWords.filter(v => (!data.excludeWords.includes(v.word)))
|
||||
if (data.wrongWords.length) {
|
||||
isTypingWrongWord.value = true
|
||||
settingStore.wordPracticeType = WordPracticeType.FollowWrite
|
||||
console.log('当前学完了,但还有错词')
|
||||
data.words = shuffle(cloneDeep(data.wrongWords))
|
||||
data.index = 0
|
||||
data.wrongWords = []
|
||||
}else {
|
||||
console.log('自由模式,全完学完了')
|
||||
showStatDialog = true
|
||||
localStorage.removeItem(PracticeSaveWordKey.key)
|
||||
}
|
||||
} else {
|
||||
data.index++
|
||||
}
|
||||
@@ -279,8 +292,8 @@ async function next(isTyping: boolean = true) {
|
||||
} else {
|
||||
isTypingWrongWord.value = false
|
||||
console.log('当前学完了,没错词', statStore.total, statStore.step, data.index)
|
||||
//学完了
|
||||
if (statStore.step === 8) {
|
||||
//学完了,这里第 7 步如果无单词,加 3 就是 9 了
|
||||
if (statStore.step >= 8) {
|
||||
statStore.spend = Date.now() - statStore.startDate
|
||||
console.log('全完学完了')
|
||||
showStatDialog = true
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { WordPracticeType, ShortcutKey, Word, WordPracticeMode } from "@/types/types.ts";
|
||||
import {WordPracticeType, ShortcutKey, Word, WordPracticeMode} from "@/types/types.ts";
|
||||
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
|
||||
import { useSettingStore } from "@/stores/setting.ts";
|
||||
import { usePlayBeep, usePlayCorrect, usePlayKeyboardAudio, usePlayWordAudio } from "@/hooks/sound.ts";
|
||||
import { emitter, EventKey, useEvents } from "@/utils/eventBus.ts";
|
||||
import { inject, onMounted, onUnmounted, Ref, watch } from "vue";
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import {usePlayBeep, usePlayCorrect, usePlayKeyboardAudio, usePlayWordAudio} from "@/hooks/sound.ts";
|
||||
import {emitter, EventKey, useEvents} from "@/utils/eventBus.ts";
|
||||
import {inject, onMounted, onUnmounted, Ref, watch} from "vue";
|
||||
import SentenceHightLightWord from "@/pages/word/components/SentenceHightLightWord.vue";
|
||||
import { usePracticeStore } from "@/stores/practice.ts";
|
||||
import { getDefaultWord } from "@/types/func.ts";
|
||||
import { _nextTick, last, sleep } from "@/utils";
|
||||
import {usePracticeStore} from "@/stores/practice.ts";
|
||||
import {getDefaultWord} from "@/types/func.ts";
|
||||
import {_nextTick, last, sleep} from "@/utils";
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
import Space from "@/pages/article/components/Space.vue";
|
||||
import Toast from "@/components/base/toast/Toast.ts";
|
||||
@@ -384,8 +384,8 @@ useEvents([
|
||||
v-if="settingStore.soundType === 'uk' && word.phonetic1">[{{ word.phonetic1 }}]
|
||||
</div>
|
||||
<VolumeIcon
|
||||
:title="`发音(${settingStore.shortcutKeyMap[ShortcutKey.PlayWordPronunciation]})`"
|
||||
ref="volumeIconRef" :simple="true" :cb="() => playWordAudio(word.word)"/>
|
||||
:title="`发音(${settingStore.shortcutKeyMap[ShortcutKey.PlayWordPronunciation]})`"
|
||||
ref="volumeIconRef" :simple="true" :cb="() => playWordAudio(word.word)"/>
|
||||
</div>
|
||||
|
||||
<div class="word my-1"
|
||||
@@ -396,13 +396,13 @@ useEvents([
|
||||
>
|
||||
<div v-if="settingStore.wordPracticeType === WordPracticeType.Dictation">
|
||||
<div class="letter text-align-center w-full inline-block"
|
||||
v-opacity="showWordResult || showFullWord">
|
||||
v-opacity="!settingStore.dictation || showWordResult || showFullWord">
|
||||
{{ word.word }}
|
||||
</div>
|
||||
<div
|
||||
class="mt-2 w-120 dictation"
|
||||
:style="{minHeight: settingStore.fontSize.wordForeignFontSize +'px'}"
|
||||
:class="showWordResult ? (right ? 'right' : 'wrong') : ''">
|
||||
class="mt-2 w-120 dictation"
|
||||
:style="{minHeight: settingStore.fontSize.wordForeignFontSize +'px'}"
|
||||
:class="showWordResult ? (right ? 'right' : 'wrong') : ''">
|
||||
<template v-for="i in input">
|
||||
<span class="l" v-if="i !== ' '">{{ i }}</span>
|
||||
<Space class="l" v-else :is-wrong="showWordResult ? (!right) : false" :is-wait="!showWordResult"/>
|
||||
@@ -412,49 +412,35 @@ useEvents([
|
||||
<template v-else>
|
||||
<span class="input" v-if="input">{{ input }}</span>
|
||||
<span class="wrong" v-if="wrong">{{ wrong }}</span>
|
||||
<template v-if="settingStore.wordPracticeMode === WordPracticeMode.System">
|
||||
<template
|
||||
v-if="[WordPracticeType.Spell,WordPracticeType.Listen,WordPracticeType.Dictation].includes(settingStore.wordPracticeType)">
|
||||
<span class="letter" v-if="!showFullWord">{{
|
||||
displayWord.split('').map(() => (WordPracticeType.Dictation === settingStore.wordPracticeType ? ' ' : '_')).join('')
|
||||
}}</span>
|
||||
<span class="letter" v-else>{{ displayWord }}</span>
|
||||
</template>
|
||||
<span class="letter" v-else>{{ displayWord }}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="letter" v-if="(settingStore.dictation && !showFullWord)">
|
||||
{{ displayWord.split('').map(() => '_').join('') }}
|
||||
<span class="letter" v-if="settingStore.dictation && !showFullWord">
|
||||
{{ displayWord.split('').map((v) => (v === ' ' ? ' ' : '_')).join('') }}
|
||||
</span>
|
||||
<span class="letter" v-else>{{ displayWord }}</span>
|
||||
</template>
|
||||
<span class="letter" v-else>{{ displayWord }}</span>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 flex gap-4"
|
||||
v-if="settingStore.wordPracticeType === WordPracticeType.Identify && !showWordResult">
|
||||
<BaseButton
|
||||
:keyboard="`快捷键(${settingStore.shortcutKeyMap[ShortcutKey.KnowWord]})`"
|
||||
size="large" @click="know">我认识
|
||||
:keyboard="`快捷键(${settingStore.shortcutKeyMap[ShortcutKey.KnowWord]})`"
|
||||
size="large" @click="know">我认识
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
:keyboard="`快捷键(${settingStore.shortcutKeyMap[ShortcutKey.UnknownWord]})`"
|
||||
size="large" @click="unknown">不认识
|
||||
:keyboard="`快捷键(${settingStore.shortcutKeyMap[ShortcutKey.UnknownWord]})`"
|
||||
size="large" @click="unknown">不认识
|
||||
</BaseButton>
|
||||
</div>
|
||||
|
||||
<div class="translate flex flex-col gap-2 my-3"
|
||||
v-opacity="settingStore.translate || ![WordPracticeType.Listen,WordPracticeType.Identify].includes(settingStore.wordPracticeType) || showWordResult || showFullWord"
|
||||
v-opacity="settingStore.translate || showWordResult || showFullWord"
|
||||
:style="{
|
||||
fontSize: settingStore.fontSize.wordTranslateFontSize +'px',
|
||||
}"
|
||||
>
|
||||
<div class="flex" v-for="(v,i) in word.trans">
|
||||
<div class="flex" v-for="v in word.trans">
|
||||
<div class="shrink-0" :class="v.pos ? 'w-12 en-article-family' : '-ml-3'">{{ v.pos }}</div>
|
||||
<span
|
||||
v-if="([WordPracticeType.Listen,WordPracticeType.Identify].includes(settingStore.wordPracticeType) || settingStore.dictation) && !(showWordResult || showFullWord)"
|
||||
v-html="hideWordInTranslation(v.cn, word.word)"></span>
|
||||
<span v-else>{{ v.cn }}</span>
|
||||
<span v-if="!settingStore.dictation || showWordResult || showFullWord">{{ v.cn }}</span>
|
||||
<span v-else v-html="hideWordInTranslation(v.cn, word.word)"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -466,35 +452,31 @@ useEvents([
|
||||
<div class="sentence" v-for="item in word.sentences">
|
||||
<SentenceHightLightWord class="text-xl" :text="item.c" :word="word.word"
|
||||
:dictation="!(!settingStore.dictation || showFullWord || showWordResult)"/>
|
||||
<div class="text-base anim"
|
||||
v-opacity="settingStore.translate || showFullWord || showWordResult">
|
||||
<div class="text-base anim" v-opacity="settingStore.translate || showFullWord || showWordResult">
|
||||
{{ item.cn }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="line-white my-2 mb-5 anim"
|
||||
v-opacity="settingStore.translate || showFullWord || showWordResult"></div>
|
||||
<div class="line-white my-2 mb-5"></div>
|
||||
</template>
|
||||
|
||||
<div class="anim"
|
||||
v-opacity="settingStore.translate || showFullWord || showWordResult">
|
||||
<template v-if="word?.phrases?.length">
|
||||
<div class="flex">
|
||||
<div class="label">短语</div>
|
||||
<div class="flex flex-col">
|
||||
<div class="flex items-center gap-4" v-for="item in word.phrases">
|
||||
<SentenceHightLightWord class="en" :text="item.c" :word="word.word"
|
||||
:dictation="!(!settingStore.dictation || showFullWord || showWordResult)"/>
|
||||
<div class="cn anim" v-opacity="settingStore.translate || showFullWord || showWordResult">{{
|
||||
item.cn
|
||||
}}
|
||||
</div>
|
||||
<template v-if="word?.phrases?.length">
|
||||
<div class="flex">
|
||||
<div class="label">短语</div>
|
||||
<div class="flex flex-col">
|
||||
<div class="flex items-center gap-4" v-for="item in word.phrases">
|
||||
<SentenceHightLightWord class="en" :text="item.c" :word="word.word"
|
||||
:dictation="!(!settingStore.dictation || showFullWord || showWordResult)"/>
|
||||
<div class="cn anim" v-opacity="settingStore.translate || showFullWord || showWordResult">
|
||||
{{ item.cn }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="line-white mt-3 mb-2"></div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="line-white mt-3 mb-2"></div>
|
||||
</template>
|
||||
|
||||
<template v-if="(settingStore.translate || !settingStore.dictation)">
|
||||
<template v-if="word?.synos?.length">
|
||||
<div class="flex">
|
||||
<div class='label'>同近义词</div>
|
||||
@@ -502,11 +484,13 @@ useEvents([
|
||||
<div class="flex" v-for="item in word.synos">
|
||||
<div class="pos line-height-1.4rem!">{{ item.pos }}</div>
|
||||
<div>
|
||||
<div class="cn">{{ item.cn }}</div>
|
||||
<div>
|
||||
<span class="en" v-for="(i,j) in item.ws">{{ i }} {{
|
||||
j !== item.ws.length - 1 ? ' / ' : ''
|
||||
}} </span>
|
||||
<div class="cn anim" v-opacity="settingStore.translate || showFullWord || showWordResult">
|
||||
{{ item.cn }}
|
||||
</div>
|
||||
<div class="anim" v-opacity="!settingStore.dictation || showFullWord || showWordResult">
|
||||
<span class="en" v-for="(i,j) in item.ws">
|
||||
{{ i }} {{ j !== item.ws.length - 1 ? ' / ' : '' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -514,7 +498,10 @@ useEvents([
|
||||
</div>
|
||||
<div class="line-white my-2"></div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<div class="anim"
|
||||
v-opacity="(settingStore.translate && !settingStore.dictation) || showFullWord || showWordResult">
|
||||
<template v-if="word?.etymology?.length">
|
||||
<div class="flex">
|
||||
<div class="label">词源</div>
|
||||
|
||||
Reference in New Issue
Block a user