wip
This commit is contained in:
@@ -59,7 +59,8 @@
|
||||
--color-third: rgb(226 232 240 / 1);
|
||||
--color-fourth: rgb(193, 193, 193);
|
||||
|
||||
--color-card-active: #FED7AA;
|
||||
//--color-card-active: #FED7AA;
|
||||
--color-card-active: rgb(253, 246, 236);
|
||||
--color-list-item-active: rgb(253, 246, 236);
|
||||
--color-icon-hightlight: rgb(12, 140, 233);
|
||||
//--color-icon-hightlight: rgb(12, 140, 233);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<template>
|
||||
<div class="flex justify-center">
|
||||
<div class="page w-[70vw] 2xl:w-[50vw]">
|
||||
<div class="page 3xl:w-[50vw] 2xl:w-[60vw] xl:w-[70vw] lg:w-[75vw]">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
33
src/components/base/OptionButton.vue
Normal file
33
src/components/base/OptionButton.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div class="w-full flex box-border cp color-white">
|
||||
<div class="option-wrap">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div class="relative group">
|
||||
<div
|
||||
class="w-10 rounded-r-lg h-full center bg-[var(--btn-primary)] hover:bg-gray border-solid border-1 border-l-gray/50 border-transparent box-border transition-all duration-300"
|
||||
>
|
||||
<IconFluentChevronDown20Regular />
|
||||
</div>
|
||||
<div
|
||||
class="space-y-2 btn-no-margin 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"
|
||||
>
|
||||
<slot name="options"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.option-wrap {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
:deep(.base-button) {
|
||||
width: 100%;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -10,7 +10,7 @@
|
||||
:disabled="isDisabled"
|
||||
/>
|
||||
<span class="radio__inner"></span>
|
||||
<span class="radio__label">
|
||||
<span class="text-sm">
|
||||
<slot>{{ label }}</slot>
|
||||
</span>
|
||||
</label>
|
||||
@@ -83,11 +83,7 @@ function onClick() {
|
||||
transition: transform 0.2s ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
.radio__label {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
|
||||
&.is-checked {
|
||||
.radio__inner {
|
||||
|
||||
@@ -23,7 +23,7 @@ provide('radioGroupValue', groupValue)
|
||||
provide('radioGroupDisabled', props.disabled)
|
||||
provide('updateRadioGroupValue', (val: string | number | boolean) => {
|
||||
if (props.disabled) return
|
||||
groupValue.value = val
|
||||
// groupValue.value = val
|
||||
emit('update:modelValue', val)
|
||||
})
|
||||
|
||||
|
||||
@@ -58,6 +58,11 @@ import {
|
||||
import { ToastInstance } from '@/components/base/toast/type.ts'
|
||||
import { watchOnce } from '@vueuse/core'
|
||||
import { setUserDictProp } from '@/apis'
|
||||
import BaseButton from '@/components/BaseButton.vue'
|
||||
import OptionButton from '@/components/base/OptionButton.vue'
|
||||
import Radio from '@/components/base/radio/Radio.vue'
|
||||
import RadioGroup from '@/components/base/radio/RadioGroup.vue'
|
||||
import GroupList from '@/pages/word/components/GroupList.vue'
|
||||
|
||||
const { isWordCollect, toggleWordCollect, isWordSimple, toggleWordSimple } = useWordOptions()
|
||||
const settingStore = useSettingStore()
|
||||
@@ -455,7 +460,7 @@ async function next(isTyping: boolean = true) {
|
||||
data.wrongWords = []
|
||||
} else {
|
||||
isTypingWrongWord.value = false
|
||||
console.log('当前学完了,没错词', statStore.total, statStore.step, data.index)
|
||||
console.log('当前学完了,没错词', statStore.total, statStore.stage, data.index)
|
||||
|
||||
const complete = () => {
|
||||
console.log('全完学完了')
|
||||
@@ -591,7 +596,6 @@ function repeat() {
|
||||
if (settingStore.wordPracticeMode === WordPracticeMode.Shuffle) {
|
||||
temp.shuffle = shuffle(temp.shuffle.filter(v => !ignoreList.includes(v.word)))
|
||||
} else {
|
||||
if (settingStore.wordPracticeMode === WordPracticeMode.System) settingStore.dictation = false
|
||||
if (store.sdict.lastLearnIndex === 0 && store.sdict.complete) {
|
||||
//如果是刚刚完成,那么学习进度要从length减回去,因为lastLearnIndex为0了,同时改complete为false
|
||||
store.sdict.lastLearnIndex = store.sdict.length - statStore.newWordNumber
|
||||
@@ -669,15 +673,14 @@ function togglePanel() {
|
||||
async function continueStudy() {
|
||||
let temp = cloneDeep(taskWords)
|
||||
//随机练习单独处理
|
||||
if (taskWords.shuffle.length) {
|
||||
if (settingStore.wordPracticeMode === WordPracticeMode.Shuffle) {
|
||||
let ignoreList = [store.allIgnoreWords, store.knownWords][settingStore.ignoreSimpleWord ? 0 : 1]
|
||||
temp.shuffle = shuffle(store.sdict.words.filter(v => !ignoreList.includes(v.word))).slice(
|
||||
0,
|
||||
runtimeStore.routeData.total
|
||||
runtimeStore.routeData.total ?? temp.shuffle.length
|
||||
)
|
||||
if (showStatDialog) showStatDialog = false
|
||||
} else {
|
||||
if (settingStore.wordPracticeMode === WordPracticeMode.System) settingStore.dictation = false
|
||||
//这里判断是否显示结算弹框,如果显示了结算弹框的话,就不用加进度了
|
||||
if (!showStatDialog) {
|
||||
console.log('没学完,强行跳过')
|
||||
@@ -699,6 +702,20 @@ async function continueStudy() {
|
||||
}
|
||||
}
|
||||
|
||||
async function jumpToGroup(group: number) {
|
||||
console.log('没学完,强行跳过',group)
|
||||
store.sdict.lastLearnIndex = (group - 1) * store.sdict.perDayStudyNumber
|
||||
emitter.emit(EventKey.resetWord)
|
||||
initData(getCurrentStudyWord())
|
||||
|
||||
if (AppEnv.CAN_REQUEST) {
|
||||
let res = await setUserDictProp(null, { ...store.sdict, type: 'word' })
|
||||
if (!res.success) {
|
||||
Toast.error(res.msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function randomWrite() {
|
||||
console.log('随机默写')
|
||||
data.words = shuffle(data.words)
|
||||
@@ -717,13 +734,7 @@ useEvents([
|
||||
[EventKey.repeatStudy, repeat],
|
||||
[EventKey.continueStudy, continueStudy],
|
||||
[EventKey.randomWrite, nextRandomWrite],
|
||||
[
|
||||
EventKey.changeDict,
|
||||
() => {
|
||||
initData(getCurrentStudyWord())
|
||||
},
|
||||
],
|
||||
|
||||
[EventKey.changeDict, () => initData(getCurrentStudyWord())],
|
||||
[ShortcutKey.ShowWord, show],
|
||||
[ShortcutKey.Previous, prev],
|
||||
[ShortcutKey.Next, skip],
|
||||
@@ -776,21 +787,29 @@ useEvents([
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
共{{ Math.ceil(store.sdict.length / store.sdict.perDayStudyNumber) }}组
|
||||
<template v-slot:panel>
|
||||
<Panel>
|
||||
<template v-slot:title>
|
||||
<!-- <span>{{ store.sdict.name }} ({{ data.index + 1 }} / {{ data.words.length }})</span>-->
|
||||
<div class="center gap-space">
|
||||
<span
|
||||
>{{ store.sdict.name }} ({{ store.sdict.lastLearnIndex }} /
|
||||
{{ store.sdict.length }})</span
|
||||
>
|
||||
<BaseIcon
|
||||
@click="continueStudy"
|
||||
:title="`下一组(${settingStore.shortcutKeyMap[ShortcutKey.NextChapter]})`"
|
||||
>
|
||||
<IconFluentArrowRight16Regular class="arrow" width="22" />
|
||||
</BaseIcon>
|
||||
<div class="center gap-1">
|
||||
<span>{{ store.sdict.name }}</span>
|
||||
|
||||
<GroupList
|
||||
@click="jumpToGroup"
|
||||
v-if="settingStore.wordPracticeMode !== WordPracticeMode.Shuffle"
|
||||
/>
|
||||
|
||||
<template v-if="taskWords.new.length">
|
||||
|
||||
|
||||
<BaseIcon
|
||||
@click="continueStudy"
|
||||
:title="`下一组(${settingStore.shortcutKeyMap[ShortcutKey.NextChapter]})`"
|
||||
>
|
||||
<IconFluentArrowRight16Regular class="arrow" width="22" />
|
||||
</BaseIcon>
|
||||
</template>
|
||||
|
||||
<BaseIcon
|
||||
@click="randomWrite"
|
||||
:title="`随机默写(${settingStore.shortcutKeyMap[ShortcutKey.RandomWrite]})`"
|
||||
|
||||
@@ -1,69 +1,68 @@
|
||||
<script setup lang="ts">
|
||||
import { useBaseStore } from "@/stores/base.ts";
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
import { ShortcutKey, Statistics, TaskWords } from "@/types/types.ts";
|
||||
import { emitter, EventKey, useEvents } from "@/utils/eventBus.ts";
|
||||
import { useSettingStore } from "@/stores/setting.ts";
|
||||
import { usePracticeStore } from "@/stores/practice.ts";
|
||||
import dayjs from "dayjs";
|
||||
import isBetween from "dayjs/plugin/isBetween";
|
||||
import { defineAsyncComponent, inject, watch } from "vue";
|
||||
import { useBaseStore } from '@/stores/base.ts'
|
||||
import BaseButton from '@/components/BaseButton.vue'
|
||||
import { ShortcutKey, Statistics, TaskWords, WordPracticeMode } from '@/types/types.ts'
|
||||
import { emitter, EventKey, useEvents } from '@/utils/eventBus.ts'
|
||||
import { useSettingStore } from '@/stores/setting.ts'
|
||||
import { usePracticeStore } from '@/stores/practice.ts'
|
||||
import dayjs from 'dayjs'
|
||||
import isBetween from 'dayjs/plugin/isBetween'
|
||||
import { defineAsyncComponent, inject, watch } from 'vue'
|
||||
import isoWeek from 'dayjs/plugin/isoWeek'
|
||||
import { msToHourMinute } from "@/utils";
|
||||
import Progress from "@/components/base/Progress.vue";
|
||||
import ChannelIcons from "@/components/ChannelIcons/ChannelIcons.vue";
|
||||
import { AppEnv } from "@/config/env.ts";
|
||||
import { addStat } from "@/apis";
|
||||
import Toast from "@/components/base/toast/Toast.ts";
|
||||
import { msToHourMinute } from '@/utils'
|
||||
import Progress from '@/components/base/Progress.vue'
|
||||
import ChannelIcons from '@/components/ChannelIcons/ChannelIcons.vue'
|
||||
import { AppEnv } from '@/config/env.ts'
|
||||
import { addStat } from '@/apis'
|
||||
import Toast from '@/components/base/toast/Toast.ts'
|
||||
|
||||
dayjs.extend(isoWeek)
|
||||
dayjs.extend(isBetween);
|
||||
dayjs.extend(isBetween)
|
||||
const Dialog = defineAsyncComponent(() => import('@/components/dialog/Dialog.vue'))
|
||||
|
||||
const store = useBaseStore()
|
||||
const settingStore = useSettingStore()
|
||||
const statStore = usePracticeStore()
|
||||
const model = defineModel({default: false})
|
||||
const model = defineModel({ default: false })
|
||||
let list = $ref([])
|
||||
let dictIsEnd = $ref(false)
|
||||
let practiceTaskWords = inject<TaskWords>('practiceTaskWords')
|
||||
|
||||
function calcWeekList() {
|
||||
// 获取本周的起止时间
|
||||
const startOfWeek = dayjs().startOf('isoWeek'); // 周一
|
||||
const endOfWeek = dayjs().endOf('isoWeek'); // 周日
|
||||
const startOfWeek = dayjs().startOf('isoWeek') // 周一
|
||||
const endOfWeek = dayjs().endOf('isoWeek') // 周日
|
||||
// 初始化 7 天的数组,默认 false
|
||||
const weekList = Array(7).fill(false);
|
||||
const weekList = Array(7).fill(false)
|
||||
|
||||
store.sdict.statistics.forEach(item => {
|
||||
const date = dayjs(item.startDate);
|
||||
const date = dayjs(item.startDate)
|
||||
if (date.isBetween(startOfWeek, endOfWeek, null, '[]')) {
|
||||
let idx = date.day();
|
||||
let idx = date.day()
|
||||
// dayjs().day() 0=周日, 1=周一, ..., 6=周六
|
||||
// 需要转换为 0=周一, ..., 6=周日
|
||||
if (idx === 0) {
|
||||
idx = 6; // 周日放到最后
|
||||
idx = 6 // 周日放到最后
|
||||
} else {
|
||||
idx = idx - 1; // 其余前移一位
|
||||
idx = idx - 1 // 其余前移一位
|
||||
}
|
||||
weekList[idx] = true;
|
||||
weekList[idx] = true
|
||||
}
|
||||
});
|
||||
weekList[2] = true;
|
||||
list = weekList;
|
||||
})
|
||||
list = weekList
|
||||
}
|
||||
|
||||
// 监听 model 弹窗打开时重新计算
|
||||
watch(model, async (newVal) => {
|
||||
watch(model, async newVal => {
|
||||
if (newVal) {
|
||||
dictIsEnd = false;
|
||||
dictIsEnd = false
|
||||
let data: Statistics = {
|
||||
spend: statStore.spend,
|
||||
startDate: statStore.startDate,
|
||||
total: statStore.total,
|
||||
wrong: statStore.wrong,
|
||||
new: statStore.newWordNumber,
|
||||
review: statStore.reviewWordNumber + statStore.writeWordNumber
|
||||
review: statStore.reviewWordNumber + statStore.writeWordNumber,
|
||||
}
|
||||
window.umami?.track('endStudyWord', {
|
||||
name: store.sdict.name,
|
||||
@@ -72,15 +71,28 @@ watch(model, async (newVal) => {
|
||||
perDayStudyNumber: store.sdict.perDayStudyNumber,
|
||||
custom: store.sdict.custom,
|
||||
complete: store.sdict.complete,
|
||||
str: `name:${store.sdict.name},per:${store.sdict.perDayStudyNumber},spend:${Number(statStore.spend / 1000 / 60).toFixed(1)},index:${store.sdict.lastLearnIndex}`
|
||||
str: `name:${store.sdict.name},per:${store.sdict.perDayStudyNumber},spend:${Number(statStore.spend / 1000 / 60).toFixed(1)},index:${store.sdict.lastLearnIndex}`,
|
||||
})
|
||||
debugger
|
||||
|
||||
//如果 shuffle 数组不为空,就说明是复习,不用修改 lastLearnIndex
|
||||
if (!practiceTaskWords.shuffle.length) {
|
||||
if (settingStore.wordPracticeMode !== WordPracticeMode.Shuffle) {
|
||||
store.sdict.lastLearnIndex = store.sdict.lastLearnIndex + statStore.newWordNumber
|
||||
if (store.sdict.lastLearnIndex >= store.sdict.length) {
|
||||
dictIsEnd = true;
|
||||
//todo 这里计算不正确,因为有可能有单词被忽略,所以需要计算忽略的单词数
|
||||
// 检查已忽略的单词数量,是否全部完成
|
||||
|
||||
let ignoreList = [store.allIgnoreWords, store.knownWords][
|
||||
settingStore.ignoreSimpleWord ? 0 : 1
|
||||
]
|
||||
// 忽略单词数
|
||||
const ignoreCount = ignoreList.filter(word =>
|
||||
store.sdict.words.some(w => w.word.toLowerCase() === word)
|
||||
).length
|
||||
// 如果lastLearnIndex已经超过可学单词数,则判定完成
|
||||
if (store.sdict.lastLearnIndex + ignoreCount >= store.sdict.length) {
|
||||
dictIsEnd = true
|
||||
store.sdict.complete = true
|
||||
store.sdict.lastLearnIndex = 0
|
||||
store.sdict.lastLearnIndex = store.sdict.length
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,11 +110,11 @@ watch(model, async (newVal) => {
|
||||
}
|
||||
|
||||
store.sdict.statistics.push(data as any)
|
||||
calcWeekList(); // 新增:计算本周学习记录
|
||||
calcWeekList() // 新增:计算本周学习记录
|
||||
}
|
||||
})
|
||||
|
||||
const close = () => model.value = false
|
||||
const close = () => (model.value = false)
|
||||
|
||||
useEvents([
|
||||
//特意注释掉,因为在练习界面用快捷键下一组时,需要判断是否在结算界面
|
||||
@@ -143,8 +155,7 @@ const formattedStudyTime = $computed(() => {
|
||||
return time.replace('小时', 'h ').replace('分钟', 'm')
|
||||
})
|
||||
|
||||
calcWeekList(); // 新增:计算本周学习记录
|
||||
|
||||
calcWeekList() // 新增:计算本周学习记录
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -153,18 +164,16 @@ calcWeekList(); // 新增:计算本周学习记录
|
||||
:close-on-click-bg="false"
|
||||
:header="false"
|
||||
:keyboard="false"
|
||||
:show-close="false">
|
||||
:show-close="false"
|
||||
>
|
||||
<div class="p-8 pr-3 bg-[var(--bg-card-primary)] rounded-2xl space-y-6">
|
||||
<!-- Header Section -->
|
||||
<div class="text-center relative">
|
||||
<div
|
||||
class="text-3xl font-bold mb-2 bg-gradient-to-r from-purple-500 to-purple-700 bg-clip-text text-transparent">
|
||||
<template v-if="practiceTaskWords.shuffle.length">
|
||||
🎯 随机复习完成
|
||||
</template>
|
||||
<template v-else>
|
||||
🎉 今日任务完成
|
||||
</template>
|
||||
class="text-3xl font-bold mb-2 bg-gradient-to-r from-purple-500 to-purple-700 bg-clip-text text-transparent"
|
||||
>
|
||||
<template v-if="practiceTaskWords.shuffle.length"> 🎯 复习完成 </template>
|
||||
<template v-else> 🎉 今日任务完成 </template>
|
||||
</div>
|
||||
<p class="font-medium text-lg">{{ encouragementText }}</p>
|
||||
</div>
|
||||
@@ -173,36 +182,37 @@ calcWeekList(); // 新增:计算本周学习记录
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<!-- Study Time -->
|
||||
<div class="item">
|
||||
<IconFluentClock20Regular class="text-purple-500"/>
|
||||
<IconFluentClock20Regular class="text-purple-500" />
|
||||
<div class="text-sm mb-1 font-medium">学习时长</div>
|
||||
<div class="text-xl font-bold">{{ formattedStudyTime }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Accuracy Rate -->
|
||||
<div class="item">
|
||||
<IconFluentTarget20Regular class="text-purple-500"/>
|
||||
<IconFluentTarget20Regular class="text-purple-500" />
|
||||
<div class="text-sm mb-1 font-medium">正确率</div>
|
||||
<div class="text-xl font-bold">{{ accuracyRate }}%</div>
|
||||
</div>
|
||||
|
||||
<!-- New Words -->
|
||||
<div class="item">
|
||||
<IconFluentSparkle20Regular class="text-purple-500"/>
|
||||
<IconFluentSparkle20Regular class="text-purple-500" />
|
||||
<div class="text-sm mb-1 font-medium">新词</div>
|
||||
<div class="text-xl font-bold ">{{ statStore.newWordNumber }}</div>
|
||||
<div class="text-xl font-bold">{{ statStore.newWordNumber }}</div>
|
||||
</div>
|
||||
|
||||
<!-- New Words -->
|
||||
<div class="item">
|
||||
<IconFluentBook20Regular class="text-purple-500"/>
|
||||
<IconFluentBook20Regular class="text-purple-500" />
|
||||
<div class="text-sm mb-1 font-medium">复习</div>
|
||||
<div class="text-xl font-bold">{{ statStore.reviewWordNumber + statStore.writeWordNumber }}</div>
|
||||
<div class="text-xl font-bold">
|
||||
{{ statStore.reviewWordNumber + statStore.writeWordNumber }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full gap-3 flex">
|
||||
<div class="space-y-6 flex-1">
|
||||
|
||||
<!-- Weekly Progress -->
|
||||
<div class="bg-[--bg-card-secend] rounded-xl p-2">
|
||||
<div class="text-center mb-4">
|
||||
@@ -216,8 +226,10 @@ calcWeekList(); // 新增:计算本周学习记录
|
||||
:class="item ? 'bg-green-500 text-white shadow-lg' : 'bg-white text-gray-700'"
|
||||
>
|
||||
<div class="font-semibold mb-1">{{ i + 1 }}</div>
|
||||
<div class="w-2 h-2 rounded-full mx-auto mb-1"
|
||||
:class="item ? 'bg-white bg-opacity-30' : 'bg-gray-300'"></div>
|
||||
<div
|
||||
class="w-2 h-2 rounded-full mx-auto mb-1"
|
||||
:class="item ? 'bg-white bg-opacity-30' : 'bg-gray-300'"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -228,44 +240,48 @@ calcWeekList(); // 新增:计算本周学习记录
|
||||
<div class="text-xl font-semibold">学习进度</div>
|
||||
<div class="text-2xl font-bold text-purple-600">{{ studyProgress }}%</div>
|
||||
</div>
|
||||
<Progress :percentage="studyProgress" size="large" :show-text="false"/>
|
||||
<Progress :percentage="studyProgress" size="large" :show-text="false" />
|
||||
<div class="flex justify-between text-sm font-medium mt-4">
|
||||
<span>已学习: {{ store.sdict.lastLearnIndex }}</span>
|
||||
<span>总词数: {{ store.sdict.length }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ChannelIcons/>
|
||||
<ChannelIcons />
|
||||
</div>
|
||||
<!-- Action Buttons -->
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||
<BaseButton
|
||||
:keyboard="settingStore.shortcutKeyMap[ShortcutKey.RepeatChapter]"
|
||||
@click="options(EventKey.repeatStudy)">
|
||||
@click="options(EventKey.repeatStudy)"
|
||||
>
|
||||
<div class="center gap-2">
|
||||
<IconFluentArrowClockwise20Regular/>
|
||||
<IconFluentArrowClockwise20Regular />
|
||||
重学一遍
|
||||
</div>
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
:keyboard="settingStore.shortcutKeyMap[ShortcutKey.NextChapter]"
|
||||
@click="options(EventKey.continueStudy)">
|
||||
@click="options(EventKey.continueStudy)"
|
||||
>
|
||||
<div class="center gap-2">
|
||||
<IconFluentPlay20Regular/>
|
||||
<IconFluentPlay20Regular />
|
||||
{{ dictIsEnd ? '从头开始练习' : '再来一组' }}
|
||||
</div>
|
||||
</BaseButton>
|
||||
<!-- todo 感觉这里的继续默写有问题,应该是当前组,而不是下一组-->
|
||||
<BaseButton
|
||||
:keyboard="settingStore.shortcutKeyMap[ShortcutKey.NextRandomWrite]"
|
||||
@click="options(EventKey.randomWrite)">
|
||||
@click="options(EventKey.randomWrite)"
|
||||
>
|
||||
<div class="center gap-2">
|
||||
<IconFluentPen20Regular/>
|
||||
<IconFluentPen20Regular />
|
||||
继续默写
|
||||
</div>
|
||||
</BaseButton>
|
||||
<BaseButton @click="$router.back">
|
||||
<div class="center gap-2">
|
||||
<IconFluentHome20Regular/>
|
||||
<IconFluentHome20Regular />
|
||||
返回主页
|
||||
</div>
|
||||
</BaseButton>
|
||||
@@ -274,7 +290,6 @@ calcWeekList(); // 新增:计算本周学习记录
|
||||
</Dialog>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
|
||||
// 移动端适配
|
||||
@media (max-width: 768px) {
|
||||
// 弹窗容器优化
|
||||
@@ -359,12 +374,10 @@ calcWeekList(); // 新增:计算本周学习记录
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
<style scoped>
|
||||
.item {
|
||||
@apply bg-[var(--bg-card-secend)] rounded-xl p-2 text-center border border-gray-100;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
_getAccomplishDate,
|
||||
_getDictDataByUrl,
|
||||
_nextTick,
|
||||
cloneDeep,
|
||||
isMobile,
|
||||
loadJsLib,
|
||||
resourceWrap,
|
||||
@@ -40,6 +41,7 @@ import { myDictList } from '@/apis'
|
||||
import PracticeWordListDialog from '@/pages/word/components/PracticeWordListDialog.vue'
|
||||
import ShufflePracticeSettingDialog from '@/pages/word/components/ShufflePracticeSettingDialog.vue'
|
||||
import { deleteDict } from '@/apis/dict.ts'
|
||||
import OptionButton from '@/components/base/OptionButton.vue'
|
||||
|
||||
const store = useBaseStore()
|
||||
const settingStore = useSettingStore()
|
||||
@@ -128,10 +130,11 @@ function startPractice(practiceMode?: WordPracticeMode): void {
|
||||
Toast.warning('没有单词可学习!')
|
||||
return
|
||||
}
|
||||
|
||||
//todo 临时处理
|
||||
localStorage.removeItem(PracticeSaveWordKey.key)
|
||||
// 如果传入了独立模式,临时设置 wordPracticeMode
|
||||
if (practiceMode !== undefined) {
|
||||
//todo 临时处理
|
||||
localStorage.removeItem(PracticeSaveWordKey.key)
|
||||
settingStore.wordPracticeMode = practiceMode
|
||||
}
|
||||
window.umami?.track('startStudyWord', {
|
||||
@@ -145,7 +148,6 @@ function startPractice(practiceMode?: WordPracticeMode): void {
|
||||
//把是否是第一次设置为false
|
||||
settingStore.first = false
|
||||
nav('practice-words/' + store.sdict.id, {}, { taskWords: currentStudy })
|
||||
// 注意:不恢复 originalMode,因为练习过程中需要保持独立模式
|
||||
} else {
|
||||
window.umami?.track('no-dict')
|
||||
Toast.warning('请先选择一本词典')
|
||||
@@ -202,7 +204,7 @@ function toggleSelect(item) {
|
||||
}
|
||||
|
||||
const progressTextLeft = $computed(() => {
|
||||
if (store.sdict.complete) return '已学完,进入总复习阶段'
|
||||
if (store.sdict.complete) return '已学完,进入复习阶段'
|
||||
return '已学习' + store.currentStudyProgress + '%'
|
||||
})
|
||||
const progressTextRight = $computed(() => {
|
||||
@@ -301,7 +303,10 @@ let isNewHost = $ref(window.location.host === Host)
|
||||
<span>已完成 {{ progressTextRight }} 词 / 共 {{ store.sdict.words.length }} 词</span>
|
||||
<span v-if="store.sdict.id">
|
||||
预计完成日期:{{
|
||||
_getAccomplishDate(store.sdict.words.length - store.sdict.lastLearnIndex, store.sdict.perDayStudyNumber)
|
||||
_getAccomplishDate(
|
||||
store.sdict.words.length - store.sdict.lastLearnIndex,
|
||||
store.sdict.perDayStudyNumber
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
@@ -388,109 +393,53 @@ let isNewHost = $ref(window.location.host === Host)
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-end mt-4 gap-4 btn-no-margin">
|
||||
<BaseButton
|
||||
size="large"
|
||||
class="flex-1"
|
||||
:disabled="!store.sdict.id"
|
||||
:loading="loading"
|
||||
@click="startPractice"
|
||||
v-if="false"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="line-height-[2]">{{ isSaveData ? '继续学习' : '开始学习' }}</span>
|
||||
<IconFluentArrowCircleRight16Regular class="text-xl" />
|
||||
</div>
|
||||
</BaseButton>
|
||||
|
||||
<div 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)] transition-all duration-300 hover:opacity-50"
|
||||
<OptionButton class="flex-2">
|
||||
<BaseButton
|
||||
size="large"
|
||||
:disabled="!store.sdict.id"
|
||||
:loading="loading"
|
||||
@click="startPractice(WordPracticeMode.System)"
|
||||
>
|
||||
<span class="line-height-[2]">{{ isSaveData ? '继续学习' : '开始学习' }}</span>
|
||||
<IconFluentArrowCircleRight16Regular class="text-xl" />
|
||||
</div>
|
||||
|
||||
<div class="relative group">
|
||||
<div
|
||||
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 transition-all duration-300"
|
||||
>
|
||||
<IconFluentChevronDown20Regular />
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="line-height-[2]">{{ isSaveData ? '继续学习' : '开始学习' }}</span>
|
||||
<IconFluentArrowCircleRight16Regular class="text-xl" />
|
||||
</div>
|
||||
</BaseButton>
|
||||
<template #options>
|
||||
<BaseButton class="w-20" @click="startPractice(WordPracticeMode.System)">
|
||||
智能
|
||||
</BaseButton>
|
||||
<BaseButton class="w-20" @click="startPractice(WordPracticeMode.FollowWriteOnly)">
|
||||
跟写
|
||||
</BaseButton>
|
||||
<BaseButton class="w-20" @click="startPractice(WordPracticeMode.IdentifyOnly)">
|
||||
自测
|
||||
</BaseButton>
|
||||
<BaseButton class="w-20" @click="startPractice(WordPracticeMode.ListenOnly)">
|
||||
听写
|
||||
</BaseButton>
|
||||
<BaseButton class="w-20" @click="startPractice(WordPracticeMode.DictationOnly)">
|
||||
默写
|
||||
</BaseButton>
|
||||
</template>
|
||||
</OptionButton>
|
||||
|
||||
<div
|
||||
class="space-y-2 btn-no-margin 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
|
||||
size="large"
|
||||
class="w-30"
|
||||
type="primary"
|
||||
@click="startPractice(WordPracticeMode.System)"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="line-height-[2]">智能</span>
|
||||
</div>
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
size="large"
|
||||
class="w-30"
|
||||
type="primary"
|
||||
@click="startPractice(WordPracticeMode.FollowWriteOnly)"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="line-height-[2]">跟写</span>
|
||||
</div>
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
size="large"
|
||||
class="w-30"
|
||||
type="primary"
|
||||
@click="startPractice(WordPracticeMode.IdentifyOnly)"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="line-height-[2]">自测</span>
|
||||
</div>
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
size="large"
|
||||
class="w-30"
|
||||
type="primary"
|
||||
@click="startPractice(WordPracticeMode.ListenOnly)"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="line-height-[2]">听写</span>
|
||||
</div>
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
size="large"
|
||||
class="w-30"
|
||||
type="primary"
|
||||
@click="startPractice(WordPracticeMode.DictationOnly)"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="line-height-[2]">默写</span>
|
||||
</div>
|
||||
</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<OptionButton class="flex-1">
|
||||
<BaseButton
|
||||
size="large"
|
||||
:loading="loading"
|
||||
@click="startPractice(WordPracticeMode.Review, true)"
|
||||
>
|
||||
复习
|
||||
</BaseButton>
|
||||
<template #options>
|
||||
<BaseButton @click="check(() => (showShufflePracticeSettingDialog = true))">
|
||||
随机复习
|
||||
</BaseButton>
|
||||
</template>
|
||||
</OptionButton>
|
||||
|
||||
<BaseButton
|
||||
size="large"
|
||||
: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
|
||||
size="large"
|
||||
:loading="loading"
|
||||
@click="startPractice(WordPracticeMode.Free)"
|
||||
>
|
||||
<BaseButton size="large" :loading="loading" @click="startPractice(WordPracticeMode.Free)">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="line-height-[2]">自由练习</span>
|
||||
<IconStreamlineColorPenDrawFlat class="text-xl" />
|
||||
|
||||
@@ -2,14 +2,17 @@
|
||||
import { inject, Ref } from 'vue'
|
||||
import { usePracticeStore } from '@/stores/practice.ts'
|
||||
import { useSettingStore } from '@/stores/setting.ts'
|
||||
import { PracticeData, ShortcutKey, WordPracticeModeStageMap, WordPracticeStage, WordPracticeStageNameMap } from '@/types/types.ts'
|
||||
import { PracticeData, ShortcutKey,
|
||||
WordPracticeMode, WordPracticeModeStageMap, WordPracticeStage, WordPracticeStageNameMap } from '@/types/types.ts'
|
||||
import BaseIcon from '@/components/BaseIcon.vue'
|
||||
import Tooltip from '@/components/base/Tooltip.vue'
|
||||
import Progress from '@/components/base/Progress.vue'
|
||||
import SettingDialog from '@/components/setting/SettingDialog.vue'
|
||||
import BaseButton from '@/components/BaseButton.vue'
|
||||
import { useBaseStore } from '@/stores/base.ts'
|
||||
|
||||
const statStore = usePracticeStore()
|
||||
const store = useBaseStore()
|
||||
const settingStore = useSettingStore()
|
||||
|
||||
defineProps<{
|
||||
@@ -34,6 +37,7 @@ function format(val: number, suffix: string = '', check: number = -1) {
|
||||
}
|
||||
|
||||
const status = $computed(() => {
|
||||
if (settingStore.wordPracticeMode === WordPracticeMode.Free) return '自由练习'
|
||||
if (isTypingWrongWord.value) return '复习错词'
|
||||
return statStore.getStageName
|
||||
})
|
||||
@@ -85,9 +89,8 @@ const progress = $computed(() => {
|
||||
</div>
|
||||
<div class="flex gap-2 justify-center items-center" id="toolbar-icons">
|
||||
<SettingDialog type="word" />
|
||||
|
||||
<BaseIcon
|
||||
v-if="statStore.step < 9"
|
||||
v-if="settingStore.wordPracticeMode !== WordPracticeMode.Free"
|
||||
@click="emit('skipStep')"
|
||||
:title="`跳到下一阶段:${WordPracticeStageNameMap[statStore.nextStage]}`"
|
||||
>
|
||||
|
||||
116
src/pages/word/components/GroupList.vue
Normal file
116
src/pages/word/components/GroupList.vue
Normal file
@@ -0,0 +1,116 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, nextTick } from 'vue'
|
||||
import RadioGroup from '@/components/base/radio/RadioGroup.vue'
|
||||
import Radio from '@/components/base/radio/Radio.vue'
|
||||
import { useBaseStore } from '@/stores/base.ts'
|
||||
const store = useBaseStore()
|
||||
|
||||
const isVisible = ref(false)
|
||||
const scrollContainer = ref<HTMLElement | null>(null)
|
||||
const itemRefs = ref<(HTMLElement | null)[]>([])
|
||||
|
||||
// 计算每个组的词数
|
||||
const getGroupWordCount = (groupIndex: number) => {
|
||||
const totalLength = store.sdict.length
|
||||
const perDay = store.sdict.perDayStudyNumber
|
||||
const totalGroups = store.groupLength
|
||||
|
||||
// 如果是最后一组且不能被整除,则显示余数
|
||||
if (groupIndex === totalGroups && totalLength % perDay !== 0) {
|
||||
return totalLength % perDay
|
||||
}
|
||||
return perDay
|
||||
}
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
isVisible.value = true
|
||||
}
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
isVisible.value = false
|
||||
}
|
||||
|
||||
// 当弹框显示时,自动滚动到选中的item
|
||||
watch(isVisible, async newVal => {
|
||||
if (newVal) {
|
||||
// 等待DOM更新和过渡动画开始
|
||||
await nextTick()
|
||||
// 再等待一小段时间确保元素已渲染
|
||||
const currentIndex = store.currentGroup - 1 // currentGroup是1-based,数组是0-based
|
||||
const targetItem = itemRefs.value[currentIndex]
|
||||
const container = scrollContainer.value
|
||||
|
||||
if (targetItem && container) {
|
||||
// 计算目标item相对于容器的位置
|
||||
const itemTop = targetItem.offsetTop
|
||||
const itemHeight = targetItem.offsetHeight
|
||||
const containerHeight = container.clientHeight
|
||||
|
||||
// 滚动到目标item,使其居中显示
|
||||
container.scrollTo({
|
||||
top: itemTop - containerHeight / 2 + itemHeight / 2,
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const setItemRef = (el: HTMLElement | null, index: number) => {
|
||||
if (el) {
|
||||
itemRefs.value[index] = el
|
||||
}
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
click: [value: number]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative z-999" @mouseenter="handleMouseEnter" @mouseleave="handleMouseLeave">
|
||||
<div
|
||||
class="pt-2 left-1/2 -transform-translate-x-1/2 absolute z-999 top-full transition-all duration-300"
|
||||
:class="{
|
||||
'opacity-0 scale-95 pointer-events-none': !isVisible,
|
||||
'opacity-100 scale-100 pointer-events-auto': isVisible,
|
||||
}"
|
||||
>
|
||||
<RadioGroup :model-value="store.currentGroup">
|
||||
<div class="card-white">
|
||||
<div ref="scrollContainer" class="h-70 overflow-y-auto space-y-2">
|
||||
<div
|
||||
:ref="el => setItemRef(el as HTMLElement, value - 1)"
|
||||
class="break-keep flex bg-primary px-3 py-1 rounded-md hover:bg-card-active anim border border-solid border-item"
|
||||
:class="{
|
||||
'bg-card-active!': value === store.currentGroup,
|
||||
}"
|
||||
@click="emit('click', value)"
|
||||
v-for="(value) in store.groupLength"
|
||||
:key="value"
|
||||
>
|
||||
<Radio :value="value" :label="`第${value}组`" />
|
||||
<span class="text-sm ml-2">{{ getGroupWordCount(value) }}词</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
<div class="target">第{{ store.currentGroup }}组</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.target {
|
||||
padding: 0.2rem 0.5rem;
|
||||
border-radius: 0.3rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
text-decoration: underline dashed gray;
|
||||
text-decoration-thickness: 2px;
|
||||
text-underline-offset: 0.3rem;
|
||||
&:hover {
|
||||
text-decoration: underline dashed transparent;
|
||||
color: white;
|
||||
background: var(--color-icon-hightlight);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -31,13 +31,11 @@ let show = $ref(false)
|
||||
let tempPerDayStudyNumber = $ref(0)
|
||||
let tempWordReviewRatio = $ref(0)
|
||||
let tempLastLearnIndex = $ref(0)
|
||||
let temPracticeMode = $ref(0)
|
||||
let tempDisableShowPracticeSettingDialog = $ref(false)
|
||||
|
||||
function changePerDayStudyNumber() {
|
||||
runtimeStore.editDict.perDayStudyNumber = tempPerDayStudyNumber
|
||||
runtimeStore.editDict.lastLearnIndex = tempLastLearnIndex
|
||||
settings.wordPracticeMode = temPracticeMode
|
||||
settings.wordReviewRatio = tempWordReviewRatio
|
||||
settings.disableShowPracticeSettingDialog = tempDisableShowPracticeSettingDialog
|
||||
emit('ok')
|
||||
@@ -50,7 +48,6 @@ watch(
|
||||
if (runtimeStore.editDict.id) {
|
||||
tempPerDayStudyNumber = runtimeStore.editDict.perDayStudyNumber
|
||||
tempLastLearnIndex = runtimeStore.editDict.lastLearnIndex
|
||||
temPracticeMode = settings.wordPracticeMode
|
||||
tempWordReviewRatio = settings.wordReviewRatio
|
||||
tempDisableShowPracticeSettingDialog = settings.disableShowPracticeSettingDialog
|
||||
} else {
|
||||
@@ -64,27 +61,6 @@ watch(
|
||||
<template>
|
||||
<Dialog v-model="model" title="学习设置" padding :footer="true" @ok="changePerDayStudyNumber">
|
||||
<div class="target-modal color-main" id="mode">
|
||||
<div class="center">
|
||||
<div class="flex gap-4 text-center h-30 w-85">
|
||||
<div
|
||||
class="mode-item"
|
||||
:class="temPracticeMode == 0 && 'active'"
|
||||
@click="temPracticeMode = 0"
|
||||
>
|
||||
<div class="title text-align-center">智能模式</div>
|
||||
<div class="desc mt-2">自动规划学习、复习、听写、默写</div>
|
||||
</div>
|
||||
<div
|
||||
class="mode-item"
|
||||
:class="temPracticeMode == 1 && 'active'"
|
||||
@click="temPracticeMode = 1"
|
||||
>
|
||||
<div class="title">自由模式</div>
|
||||
<div class="desc mt-2">自由练习,系统不强制复习与默写</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center mt-4">
|
||||
<span
|
||||
>共<span class="target-number">{{ runtimeStore.editDict.length }}</span
|
||||
@@ -179,7 +155,7 @@ watch(
|
||||
<style scoped lang="scss">
|
||||
.target-modal {
|
||||
width: 35rem;
|
||||
|
||||
|
||||
|
||||
.mode-item {
|
||||
@apply w-50% border border-blue border-solid p-2 rounded-lg cursor-pointer;
|
||||
|
||||
@@ -1,35 +1,71 @@
|
||||
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 { 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";
|
||||
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 {
|
||||
simpleWords: string[],
|
||||
simpleWords: string[]
|
||||
load: boolean
|
||||
word: {
|
||||
studyIndex: number,
|
||||
bookList: Dict[],
|
||||
},
|
||||
studyIndex: number
|
||||
bookList: Dict[]
|
||||
}
|
||||
article: {
|
||||
bookList: Dict[],
|
||||
studyIndex: number,
|
||||
},
|
||||
bookList: Dict[]
|
||||
studyIndex: number
|
||||
}
|
||||
dictListVersion: number
|
||||
}
|
||||
|
||||
export const getDefaultBaseState = (): BaseState => ({
|
||||
simpleWords: [
|
||||
'a', 'an',
|
||||
'i', 'my', 'me', 'you', 'your', 'he', 'his', 'she', 'her', 'it',
|
||||
'what', 'who', 'where', 'how', 'when', 'which',
|
||||
'be', 'am', 'is', 'was', 'are', 'were', 'do', 'did', 'can', 'could', 'will', 'would',
|
||||
'the', 'that', 'this', 'and', 'not', 'no', 'yes',
|
||||
'to', 'of', 'for', 'at', 'in'
|
||||
'a',
|
||||
'an',
|
||||
'i',
|
||||
'my',
|
||||
'me',
|
||||
'you',
|
||||
'your',
|
||||
'he',
|
||||
'his',
|
||||
'she',
|
||||
'her',
|
||||
'it',
|
||||
'what',
|
||||
'who',
|
||||
'where',
|
||||
'how',
|
||||
'when',
|
||||
'which',
|
||||
'be',
|
||||
'am',
|
||||
'is',
|
||||
'was',
|
||||
'are',
|
||||
'were',
|
||||
'do',
|
||||
'did',
|
||||
'can',
|
||||
'could',
|
||||
'will',
|
||||
'would',
|
||||
'the',
|
||||
'that',
|
||||
'this',
|
||||
'and',
|
||||
'not',
|
||||
'no',
|
||||
'yes',
|
||||
'to',
|
||||
'of',
|
||||
'for',
|
||||
'at',
|
||||
'in',
|
||||
],
|
||||
load: false,
|
||||
word: {
|
||||
@@ -40,18 +76,18 @@ export const getDefaultBaseState = (): BaseState => ({
|
||||
id: DictId.wordKnown,
|
||||
en_name: DictId.wordCollect,
|
||||
name: '已掌握',
|
||||
description: '已掌握后的单词不会出现在练习中'
|
||||
description: '已掌握后的单词不会出现在练习中',
|
||||
}),
|
||||
],
|
||||
studyIndex: -1,
|
||||
},
|
||||
article: {
|
||||
bookList: [
|
||||
getDefaultDict({ id: DictId.articleCollect, en_name: DictId.articleCollect, name: '收藏' })
|
||||
getDefaultDict({ id: DictId.articleCollect, en_name: DictId.articleCollect, name: '收藏' }),
|
||||
],
|
||||
studyIndex: -1,
|
||||
},
|
||||
dictListVersion: 1
|
||||
dictListVersion: 1,
|
||||
})
|
||||
|
||||
export const useBaseStore = defineStore('base', {
|
||||
@@ -75,7 +111,9 @@ export const useBaseStore = defineStore('base', {
|
||||
return this.known.words.map((v: Word) => v.word.toLowerCase())
|
||||
},
|
||||
allIgnoreWords() {
|
||||
return this.known.words.map((v: Word) => v.word.toLowerCase()).concat(this.simpleWords.map((v: string) => v.toLowerCase()))
|
||||
return this.known.words
|
||||
.map((v: Word) => v.word.toLowerCase())
|
||||
.concat(this.simpleWords.map((v: string) => v.toLowerCase()))
|
||||
},
|
||||
sdict(): Dict {
|
||||
if (this.word.studyIndex >= 0) {
|
||||
@@ -83,6 +121,15 @@ export const useBaseStore = defineStore('base', {
|
||||
}
|
||||
return getDefaultDict()
|
||||
},
|
||||
groupLength(): number {
|
||||
return Math.ceil(this.sdict.length / this.sdict.perDayStudyNumber)
|
||||
},
|
||||
currentGroup(): number {
|
||||
//当能除尽时,应该加1
|
||||
let s = this.sdict.lastLearnIndex % this.sdict.perDayStudyNumber
|
||||
let d = this.sdict.lastLearnIndex / this.sdict.perDayStudyNumber
|
||||
return Math.floor(s === 0 ?( d + 1) : d)
|
||||
},
|
||||
currentStudyProgress(): number {
|
||||
if (!this.sdict.length) return 0
|
||||
return _getStudyProgress(this.sdict.lastLearnIndex, this.sdict.length)
|
||||
@@ -90,7 +137,9 @@ export const useBaseStore = defineStore('base', {
|
||||
getDictCompleteDate(): number {
|
||||
if (!this.sdict.length) return 0
|
||||
if (!this.sdict.perDayStudyNumber) return 0
|
||||
return Math.ceil((this.sdict.length - this.sdict.lastLearnIndex) / this.sdict.perDayStudyNumber)
|
||||
return Math.ceil(
|
||||
(this.sdict.length - this.sdict.lastLearnIndex) / this.sdict.perDayStudyNumber
|
||||
)
|
||||
},
|
||||
sbook(): Dict {
|
||||
return this.article.bookList[this.article.studyIndex] ?? {}
|
||||
|
||||
@@ -232,6 +232,7 @@ export enum WordPracticeMode {
|
||||
ListenOnly = 4, // 独立听写模式
|
||||
FollowWriteOnly = 5, // 独立跟写模式(内部会自动切换到 Spell)
|
||||
Shuffle = 6, // 随机复习模式
|
||||
Review = 7, // 复习模式
|
||||
}
|
||||
|
||||
//练习类型
|
||||
@@ -321,6 +322,15 @@ export const WordPracticeModeStageMap: Record<WordPracticeMode, WordPracticeStag
|
||||
WordPracticeStage.Shuffle,
|
||||
WordPracticeStage.Complete,
|
||||
],
|
||||
[WordPracticeMode.Review]: [
|
||||
WordPracticeStage.IdentifyReview,
|
||||
WordPracticeStage.ListenReview,
|
||||
WordPracticeStage.DictationReview,
|
||||
WordPracticeStage.IdentifyReviewAll,
|
||||
WordPracticeStage.ListenReviewAll,
|
||||
WordPracticeStage.DictationReviewAll,
|
||||
WordPracticeStage.Complete,
|
||||
],
|
||||
}
|
||||
|
||||
export const WordPracticeStageNameMap: Record<WordPracticeStage, string> = {
|
||||
@@ -336,6 +346,6 @@ export const WordPracticeStageNameMap: Record<WordPracticeStage, string> = {
|
||||
[WordPracticeStage.IdentifyReviewAll]: '自测之前学习',
|
||||
[WordPracticeStage.ListenReviewAll]: '听写之前学习',
|
||||
[WordPracticeStage.DictationReviewAll]: '默写之前学习',
|
||||
[WordPracticeStage.Complete]: '学习完成',
|
||||
[WordPracticeStage.Complete]: '完成学习',
|
||||
[WordPracticeStage.Shuffle]: '随机复习',
|
||||
}
|
||||
|
||||
@@ -22,4 +22,17 @@ export default defineConfig({
|
||||
presets: [
|
||||
presetWind3(),
|
||||
],
|
||||
// 自定义断点
|
||||
theme: {
|
||||
breakpoints: {
|
||||
'xs': '480px', // 自定义小断点
|
||||
'sm': '640px',
|
||||
'md': '768px',
|
||||
'lg': '1024px',
|
||||
'xl': '1280px',
|
||||
'2xl': '1536px',
|
||||
'3xl': '1920px',
|
||||
'4k': '2560px',
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user