This commit is contained in:
Zyronon
2025-11-16 20:53:32 +08:00
parent 9032711601
commit 4f7ecbea29
17 changed files with 137 additions and 97 deletions

View File

@@ -5,14 +5,13 @@ import { useRuntimeStore } from "@/stores/runtime.ts";
import { useSettingStore } from "@/stores/setting.ts";
import useTheme from "@/hooks/theme.ts";
import { shakeCommonDict } from "@/utils";
import { routes } from "@/router.ts";
import { get, set } from 'idb-keyval'
import { useRoute } from "vue-router";
import { DictId } from "@/types/types.ts";
import { APP_VERSION, CAN_REQUEST, LOCAL_FILE_KEY, SAVE_DICT_KEY, SAVE_SETTING_KEY } from "@/config/env.ts";
import { APP_VERSION, AppEnv, LOCAL_FILE_KEY, SAVE_DICT_KEY, SAVE_SETTING_KEY } from "@/config/env.ts";
import { syncSetting } from "@/apis";
import {useUserStore} from "@/stores/auth.ts";
import { useUserStore } from "@/stores/auth.ts";
const store = useBaseStore()
const runtimeStore = useRuntimeStore()
@@ -53,7 +52,7 @@ watch(store.$state, (n: BaseState) => {
watch(settingStore.$state, (n) => {
set(SAVE_SETTING_KEY.key, JSON.stringify({val: n, version: SAVE_SETTING_KEY.version}))
if (CAN_REQUEST) {
if (AppEnv.CAN_REQUEST) {
syncSetting(null, settingStore.$state)
}
})

View File

@@ -98,19 +98,19 @@ defineEmits(['click'])
}
}
&:hover:not(.disabled) {
opacity: .6;
}
&.primary {
background: var(--btn-primary);
&:hover:not(.disabled) {
opacity: 0.6;
}
}
&.link {
border-radius: 0;
border-bottom: 2px solid transparent;
&:hover {
&:hover:not(.disabled) {
border-bottom: 2px solid var(--color-font-2);
}
}
@@ -119,11 +119,20 @@ defineEmits(['click'])
background: var(--btn-info);
border: 1px solid var(--color-main-text);
color: var(--color-main-text);
&:hover:not(.disabled) {
opacity: 0.6;
}
}
&.orange {
background: #FACC15;
color: black;
&:hover:not(.disabled) {
background: #fbe27e;
color: rgba(0, 0, 0, 0.6);
}
}
&.active {

View File

@@ -14,15 +14,10 @@ const map = {
}
export const ENV = Object.assign(map['DEV'], common)
// export const IS_OFFICIAL = import.meta.env.DEV
// export let IS_LOGIN = true
export let IS_OFFICIAL = true
export let IS_LOGIN = (!!localStorage.getItem('token')) || false
export let CAN_REQUEST = IS_LOGIN && IS_OFFICIAL
export let AppEnv = {
TOKEN: localStorage.getItem('token') ?? '',
IS_OFFICIAL: true,
IS_OFFICIAL: false,
IS_LOGIN: false,
CAN_REQUEST: false
}

View File

@@ -18,7 +18,7 @@ import dayjs from "dayjs";
import isBetween from "dayjs/plugin/isBetween";
import isoWeek from 'dayjs/plugin/isoWeek'
import { useFetch } from "@vueuse/core";
import { CAN_REQUEST, DICT_LIST, PracticeSaveArticleKey } from "@/config/env.ts";
import { AppEnv, DICT_LIST, PracticeSaveArticleKey } from "@/config/env.ts";
import { myDictList } from "@/apis";
dayjs.extend(isoWeek)
@@ -36,7 +36,7 @@ watch(() => store.load, n => {
}, {immediate: true})
async function init() {
if (CAN_REQUEST) {
if (AppEnv.CAN_REQUEST) {
let res = await myDictList({type: "article"})
if (res.success) {
store.setState(Object.assign(store.$state, res.data))

View File

@@ -20,7 +20,7 @@ import ArticleAudio from "@/pages/article/components/ArticleAudio.vue";
import { MessageBox } from "@/utils/MessageBox.tsx";
import { useSettingStore } from "@/stores/setting.ts";
import { useFetch } from "@vueuse/core";
import { CAN_REQUEST, DICT_LIST } from "@/config/env.ts";
import { AppEnv, DICT_LIST } from "@/config/env.ts";
import { detail } from "@/apis";
const runtimeStore = useRuntimeStore()
@@ -93,7 +93,7 @@ async function init() {
}
if (base.article.bookList.find(book => book.id === runtimeStore.editDict.id)) {
if (CAN_REQUEST) {
if (AppEnv.CAN_REQUEST) {
let res = await detail({id: runtimeStore.editDict.id})
if (res.success) {
runtimeStore.editDict.statistics = res.data.statistics

View File

@@ -34,7 +34,7 @@ import { useRoute, useRouter } from "vue-router";
import PracticeLayout from "@/components/PracticeLayout.vue";
import ArticleAudio from "@/pages/article/components/ArticleAudio.vue";
import VolumeSetting from "@/pages/article/components/VolumeSetting.vue";
import { CAN_REQUEST, DICT_LIST, PracticeSaveArticleKey } from "@/config/env.ts";
import { AppEnv, DICT_LIST, PracticeSaveArticleKey } from "@/config/env.ts";
import { addStat, setDictProp } from "@/apis";
import { useRuntimeStore } from "@/stores/runtime.ts";
@@ -254,7 +254,7 @@ async function complete() {
wrong: statStore.wrong,
}
if (CAN_REQUEST) {
if (AppEnv.CAN_REQUEST) {
let res = await addStat({...data, type: 'article'})
if (!res.success) {
Toast.error(res.msg)
@@ -337,7 +337,7 @@ async function changeArticle(val: ArticleItem) {
store.sbook.lastLearnIndex = rIndex
getCurrentPractice()
if (CAN_REQUEST) {
if (AppEnv.CAN_REQUEST) {
let res = await setDictProp(null, store.sbook)
if (!res.success) {
Toast.error(res.msg)

View File

@@ -12,8 +12,8 @@ import { Option, Select } from "@/components/base/select";
import BaseInput from "@/components/base/BaseInput.vue";
import Form from "@/components/base/form/Form.vue";
import FormItem from "@/components/base/form/FormItem.vue";
import { CAN_REQUEST } from "@/config/env.ts";
import { addDict } from "@/apis";
import { AppEnv } from "@/config/env.ts";
const props = defineProps<{
isAdd: boolean,
@@ -58,7 +58,7 @@ async function onSubmit() {
Toast.warning('已有相同名称!')
return
} else {
if (CAN_REQUEST) {
if (AppEnv.CAN_REQUEST) {
loading = true
let res = await addDict(null, data)
loading = false

View File

@@ -728,7 +728,7 @@ function importOldData() {
<ol>
<li>
<div class="title"><b>智能模式优化</b></div>
<div class="desc">练习时新增四种练习模式学习复习听写默写</div>
<div class="desc">练习时新增四种练习模式学习辨认听写默写</div>
</li>
<li>
<div class="title"><b>学习模式</b></div>
@@ -741,7 +741,7 @@ function importOldData() {
</div>
</li>
<li>
<div class="title"><b>复习模式新增</b></div>
<div class="title"><b>辨认模式新增</b></div>
<div class="desc">
<ul>
<li>仅在复习已学单词时出现</li>

View File

@@ -490,6 +490,7 @@ function subscribe() {
:placeholder="`请输入新密码(${PASSWORD_CONFIG.minLength}-${PASSWORD_CONFIG.maxLength}位)`"
:min="PASSWORD_CONFIG.minLength"
:max="PASSWORD_CONFIG.maxLength"
autofocus
/>
</FormItem>
<FormItem prop="confirmPwd">

View File

@@ -26,7 +26,7 @@ import { getCurrentStudyWord } from "@/hooks/dict.ts";
import PracticeSettingDialog from "@/pages/word/components/PracticeSettingDialog.vue";
import { useSettingStore } from "@/stores/setting.ts";
import { MessageBox } from "@/utils/MessageBox.tsx";
import { CAN_REQUEST, Origin, PracticeSaveWordKey } from "@/config/env.ts";
import { AppEnv, Origin, PracticeSaveWordKey } from "@/config/env.ts";
import { detail } from "@/apis";
const runtimeStore = useRuntimeStore()
@@ -196,7 +196,7 @@ onMounted(async () => {
}
if (base.word.bookList.find(book => book.id === runtimeStore.editDict.id)) {
if (CAN_REQUEST) {
if (AppEnv.CAN_REQUEST) {
let res = await detail({id: runtimeStore.editDict.id})
if (res.success) {
runtimeStore.editDict.statistics = res.data.statistics

View File

@@ -248,9 +248,7 @@ function goNextStep(originList, mode, msg) {
}
async function next(isTyping: boolean = true) {
if (isTyping) {
statStore.inputWordNumber++
}
if (isTyping) statStore.inputWordNumber++
if (settingStore.wordPracticeMode === WordPracticeMode.Free) {
if (data.index === data.words.length - 1) {
data.wrongWords = data.wrongWords.filter(v => (!data.excludeWords.includes(v.word)))
@@ -310,9 +308,9 @@ async function next(isTyping: boolean = true) {
return goNextStep(shuffle(taskWords.write), WordPracticeType.Listen, '开始听写之前')
}
//开始复写之前
//开始辨认之前
if (statStore.step === 5) {
return goNextStep(taskWords.write, WordPracticeType.Identify, '开始复写之前')
return goNextStep(taskWords.write, WordPracticeType.Identify, '开始辨认之前')
}
//开始默写上次
@@ -325,9 +323,9 @@ async function next(isTyping: boolean = true) {
return goNextStep(shuffle(taskWords.review), WordPracticeType.Listen, '开始听写上次')
}
//开始复写昨日
//开始辨认昨日
if (statStore.step === 2) {
return goNextStep(taskWords.review, WordPracticeType.Identify, '开始复写昨日')
return goNextStep(taskWords.review, WordPracticeType.Identify, '开始辨认昨日')
}
//开始默写新词
@@ -352,6 +350,13 @@ async function next(isTyping: boolean = true) {
savePracticeData()
}
function skipStep(){
data.index = data.words.length - 1
settingStore.wordPracticeType = WordPracticeType.Spell
data.wrongWords = []
next(false)
}
function onWordKnow() {
//标记模式时,用户认识的单词加入到排除里面,后续不再复习
let rIndex = data.excludeWords.findIndex(v => v === word.word)
@@ -649,6 +654,7 @@ useEvents([
:is-collect="isWordCollect(word)"
@toggle-collect="toggleWordCollect(word)"
@skip="next(false)"
@skipStep="skipStep"
/>
</template>
</PracticeLayout>

View File

@@ -19,11 +19,10 @@ import PracticeSettingDialog from "@/pages/word/components/PracticeSettingDialog
import ChangeLastPracticeIndexDialog from "@/pages/word/components/ChangeLastPracticeIndexDialog.vue";
import { useSettingStore } from "@/stores/setting.ts";
import { useFetch } from "@vueuse/core";
import { CAN_REQUEST, DICT_LIST, PracticeSaveWordKey } from "@/config/env.ts";
import { AppEnv, DICT_LIST, PracticeSaveWordKey } from "@/config/env.ts";
import { myDictList } from "@/apis";
import PracticeWordListDialog from "@/pages/word/components/PracticeWordListDialog.vue";
import ShufflePracticeSettingDialog from "@/pages/word/components/ShufflePracticeSettingDialog.vue";
import Header from "@/components/Header.vue";
const store = useBaseStore()
@@ -45,7 +44,7 @@ watch(() => store.load, n => {
}, {immediate: true})
async function init() {
if (CAN_REQUEST) {
if (AppEnv.CAN_REQUEST) {
let res = await myDictList({type: "word"})
if (res.success) {
store.setState(Object.assign(store.$state, res.data))
@@ -262,12 +261,12 @@ const {
<div class="flex-1" :class="!store.sdict.id && 'opacity-30 cursor-not-allowed'">
<div class="flex justify-between">
<div class="flex items-center gap-3">
<div class="flex items-center gap-2">
<div class="p-2 center rounded-full bg-white ">
<IconFluentStar20Filled class="text-lg color-amber"/>
</div>
<div class="text-xl font-bold">
{{ isSaveData ? '上次学习任务' : '今日任务' }}
{{ isSaveData ? '上次任务' : '今日任务' }}
</div>
<span class="color-link cursor-pointer"
v-if="store.sdict.id"
@@ -323,52 +322,62 @@ const {
</BaseButton>
<div
class="w-full flex box-border rounded-lg cp color-white">
<div class="flex-1 center gap-2 py-1 bg-[var(--btn-primary)] hover:opacity-50">
class="w-full flex box-border cp color-white">
<div
@click="startPractice"
class="flex-1 rounded-l-lg center gap-2 py-1 bg-[var(--btn-primary)] hover:opacity-50">
<span class="line-height-[2]">{{ isSaveData ? '继续学习' : '开始学习' }}</span>
<IconFluentArrowCircleRight16Regular class="text-xl"/>
</div>
<div class="relative group">
<div
class="w-10 h-full center bg-[var(--btn-primary)] hover:bg-gray border-solid border-2 border-l-gray border-transparent box-border">
class="w-10 rounded-r-lg h-full center bg-[var(--btn-primary)] hover:bg-gray border-solid border-2 border-l-gray border-transparent box-border">
<IconFluentChevronDown20Regular/>
</div>
<div
class="absolute z-2 left-1/2 -translate-x-1/2 mt-2 w-40 bg-white border rounded shadow opacity-110 scale-95
class="space-y-2 pt-2 absolute z-2 right-0 border rounded opacity-0 scale-95
group-hover:opacity-100 group-hover:scale-100
transition-all duration-150 pointer-events-none group-hover:pointer-events-auto"
>
<BaseButton
v-for="i in 3"
size="large" type="orange"
:loading="loading"
@click="check(()=>showShufflePracticeSettingDialog = true)">
<div class="flex items-center gap-2">
<span class="line-height-[2]">随机复习</span>
<IconFluentArrowShuffle20Filled class="text-xl"/>
</div>
</BaseButton>
<div>
<BaseButton
size="large" type="orange"
:loading="loading"
@click="check(()=>showShufflePracticeSettingDialog = true)">
<div class="flex items-center gap-2">
<span class="line-height-[2]">随机复习</span>
<IconFluentArrowShuffle20Filled class="text-xl"/>
</div>
</BaseButton>
</div>
<div>
<BaseButton
size="large" type="orange"
:loading="loading"
@click="check(()=>showShufflePracticeSettingDialog = true)">
<div class="flex items-center gap-2">
<span class="line-height-[2]">重新学习</span>
<IconFluentArrowShuffle20Filled class="text-xl"/>
</div>
</BaseButton>
</div>
</div>
</div>
</div>
<BaseButton
v-if="store.sdict.id && store.sdict.lastLearnIndex"
size="large" type="orange"
:loading="loading"
@click="check(()=>showShufflePracticeSettingDialog = true)">
<div class="flex items-center gap-2">
<span class="line-height-[2]">随机复习</span>
<IconFluentArrowShuffle20Filled class="text-xl"/>
</div>
</BaseButton>
<!-- <BaseButton-->
<!-- v-if="store.sdict.id && store.sdict.lastLearnIndex"-->
<!-- size="large" type="orange"-->
<!-- :loading="loading"-->
<!-- @click="check(()=>showShufflePracticeSettingDialog = true)">-->
<!-- <div class="flex items-center gap-2">-->
<!-- <span class="line-height-[2]">随机复习</span>-->
<!-- <IconFluentArrowShuffle20Filled class="text-xl"/>-->
<!-- </div>-->
<!-- </BaseButton>-->
</div>
</div>
</div>

View File

@@ -3,7 +3,7 @@
import { inject, Ref, watch } from "vue"
import { usePracticeStore } from "@/stores/practice.ts";
import { useSettingStore } from "@/stores/setting.ts";
import {PracticeData, WordPracticeType, ShortcutKey, TaskWords} from "@/types/types.ts";
import { PracticeData, WordPracticeType, ShortcutKey, TaskWords } from "@/types/types.ts";
import BaseIcon from "@/components/BaseIcon.vue";
import Tooltip from "@/components/base/Tooltip.vue";
import Progress from '@/components/base/Progress.vue'
@@ -22,6 +22,7 @@ const emit = defineEmits<{
toggleSimple: [],
edit: [],
skip: [],
skipStep:[]
}>()
let practiceData = inject<PracticeData>('practiceData')
@@ -33,8 +34,12 @@ function format(val: number, suffix: string = '', check: number = -1) {
const status = $computed(() => {
if (isTypingWrongWord.value) return '复习错词'
return getStepStr(statStore.step)
})
function getStepStr(step: number) {
let str = ''
switch (statStore.step) {
switch (step) {
case 0:
str += `学习新词`
break
@@ -45,7 +50,7 @@ const status = $computed(() => {
str += `默写新词`
break
case 3:
str += `复习上次学习`
str += `辨认上次学习`
break
case 4:
str += '听写上次学习'
@@ -54,7 +59,7 @@ const status = $computed(() => {
str += '默写上次学习'
break
case 6:
str += '复习之前学习'
str += '辨认之前学习'
break
case 7:
str += '听写之前学习'
@@ -62,12 +67,15 @@ const status = $computed(() => {
case 8:
str += '默写之前学习'
break
case 9:
str += '学习完成'
break
case 10:
str += '随机复习'
break
}
return str
})
}
const progress = $computed(() => {
if (!practiceData.words.length) return 0
@@ -115,6 +123,13 @@ const progress = $computed(() => {
</div>
</div>
<div class="flex gap-2 justify-center items-center">
<BaseIcon
v-if="statStore.step < 9"
@click="emit('skipStep')"
:title="`跳到下一阶段:${getStepStr(statStore.step+1)}`">
<IconFluentArrowRight16Regular/>
</BaseIcon>
<BaseIcon
:class="!isSimple?'collect':'fill'"
@click="$emit('toggleSimple')"
@@ -132,7 +147,7 @@ const progress = $computed(() => {
</BaseIcon>
<BaseIcon
@click="emit('skip')"
:title="`跳过(${settingStore.shortcutKeyMap[ShortcutKey.Next]})`">
:title="`跳过当前单词(${settingStore.shortcutKeyMap[ShortcutKey.Next]})`">
<IconFluentArrowBounce20Regular class="transform-rotate-180"/>
</BaseIcon>
@@ -207,7 +222,6 @@ const progress = $computed(() => {
flex-direction: column;
align-items: center;
gap: .3rem;
width: 6rem;
color: gray;
.line {

View File

@@ -189,6 +189,7 @@ async function onTyping(e: KeyboardEvent) {
}
inputLock = true
let letter = e.key
console.log('letter',letter)
//默写特殊逻辑
if (settingStore.wordPracticeType === WordPracticeType.Dictation) {
if (e.code === 'Space') {
@@ -221,6 +222,13 @@ async function onTyping(e: KeyboardEvent) {
playKeyboardAudio()
updateCurrentWordInfo();
inputLock = false
} else if (settingStore.wordPracticeType === WordPracticeType.Identify && !showWordResult) {
//当辨认模式下按1和2会单独处理如果按其他键则自动默认为不认识
showWordResult = true
emit('wrong')
if (settingStore.wordSound) volumeIconRef?.play()
inputLock = false
onTyping(e)
} else {
let right = false
if (settingStore.ignoreCase) {

View File

@@ -1,11 +1,11 @@
import {defineStore} from 'pinia'
import {Dict, DictId, Word} from "../types/types.ts"
import {_getStudyProgress, checkAndUpgradeSaveDict, shakeCommonDict} from "@/utils";
import {shallowReactive} from "vue";
import {getDefaultDict} from "@/types/func.ts";
import {get, set} from 'idb-keyval'
import {CAN_REQUEST, IS_OFFICIAL, SAVE_DICT_KEY} from "@/config/env.ts";
import {add2MyDict, dictListVersion, myDictList} from "@/apis";
import { defineStore } from 'pinia'
import { Dict, DictId, Word } from "../types/types.ts"
import { _getStudyProgress, checkAndUpgradeSaveDict, shakeCommonDict } from "@/utils";
import { shallowReactive } from "vue";
import { getDefaultDict } from "@/types/func.ts";
import { get, set } from 'idb-keyval'
import { AppEnv, SAVE_DICT_KEY } from "@/config/env.ts";
import { add2MyDict, dictListVersion, myDictList } from "@/apis";
import Toast from "@/components/base/toast/Toast.ts";
export interface BaseState {
@@ -125,13 +125,13 @@ export const useBaseStore = defineStore('base', {
try {
let configStr: string = await get(SAVE_DICT_KEY.key)
let data = checkAndUpgradeSaveDict(configStr)
if (IS_OFFICIAL) {
if (AppEnv.IS_OFFICIAL) {
let r = await dictListVersion()
if (r.success) {
data.dictListVersion = r.data
}
}
if (CAN_REQUEST) {
if (AppEnv.CAN_REQUEST) {
let res = await myDictList()
if (res.success) {
Object.assign(data, res.data)
@@ -147,7 +147,7 @@ export const useBaseStore = defineStore('base', {
},
//改变词典
async changeDict(val: Dict) {
if (CAN_REQUEST) {
if (AppEnv.CAN_REQUEST) {
let r = await add2MyDict(val)
if (!r.success) {
return Toast.error(r.msg)
@@ -175,7 +175,7 @@ export const useBaseStore = defineStore('base', {
},
//改变书籍
async changeBook(val: Dict) {
if (CAN_REQUEST) {
if (AppEnv.CAN_REQUEST) {
let r = await add2MyDict(val)
if (!r.success) {
return Toast.error(r.msg)

View File

@@ -1,9 +1,9 @@
import {defineStore} from "pinia"
import {checkAndUpgradeSaveSetting, cloneDeep} from "@/utils";
import {DefaultShortcutKeyMap, WordPracticeMode, WordPracticeType} from "@/types/types.ts";
import {get} from "idb-keyval";
import {CAN_REQUEST, SAVE_SETTING_KEY} from "@/config/env.ts";
import {getSetting} from "@/apis";
import { defineStore } from "pinia"
import { checkAndUpgradeSaveSetting, cloneDeep } from "@/utils";
import { DefaultShortcutKeyMap, WordPracticeMode, WordPracticeType } from "@/types/types.ts";
import { get } from "idb-keyval";
import { AppEnv, SAVE_SETTING_KEY } from "@/config/env.ts";
import { getSetting } from "@/apis";
export interface SettingState {
soundType: string,
@@ -119,7 +119,7 @@ export const useSettingStore = defineStore('setting', {
return new Promise(async resolve => {
let configStr = await get(SAVE_SETTING_KEY.key)
let data = checkAndUpgradeSaveSetting(configStr)
if (CAN_REQUEST) {
if (AppEnv.CAN_REQUEST) {
let res = await getSetting()
if (res.success) {
Object.assign(data, res.data)

View File

@@ -4,8 +4,7 @@ import { Dict, DictId, DictResource, DictType } from "@/types/types.ts";
import { useRouter } from "vue-router";
import { useRuntimeStore } from "@/stores/runtime.ts";
import dayjs from 'dayjs'
import axios from "axios";
import { ENV, IS_OFFICIAL, RESOURCE_PATH, SAVE_DICT_KEY, SAVE_SETTING_KEY } from "@/config/env.ts";
import { AppEnv, RESOURCE_PATH, SAVE_DICT_KEY, SAVE_SETTING_KEY } from "@/config/env.ts";
import { nextTick } from "vue";
import Toast from '@/components/base/toast/Toast.ts'
import { getDefaultDict, getDefaultWord } from "@/types/func.ts";
@@ -440,7 +439,7 @@ export function total(arr, key) {
}
export function resourceWrap(resource: string, version?: number) {
if (IS_OFFICIAL) {
if (AppEnv.IS_OFFICIAL) {
if (resource.includes('.json')) resource = resource.replace('.json', '');
if (!resource.includes('http')) resource = RESOURCE_PATH + resource
if (version === undefined) {