fix:a new free learning mode has been added
This commit is contained in:
@@ -17,14 +17,14 @@ import DeleteIcon from "@/components/icon/DeleteIcon.vue";
|
||||
let list = defineModel('list')
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
showBorder?: boolean
|
||||
loading?: boolean
|
||||
showToolbar?: boolean
|
||||
del?: Function
|
||||
batchDel?: Function
|
||||
add?: Function
|
||||
}>(), {
|
||||
showBorder: false,
|
||||
loading: true,
|
||||
showToolbar: true,
|
||||
del: () => void 0,
|
||||
add: () => void 0,
|
||||
batchDel: () => void 0
|
||||
@@ -127,102 +127,101 @@ defineRender(
|
||||
|
||||
return (
|
||||
<div class="flex flex-col gap-3">
|
||||
<div>
|
||||
{
|
||||
showSearchInput ? (
|
||||
<div
|
||||
class="flex gap-4"
|
||||
>
|
||||
<Input
|
||||
prefixIcon
|
||||
modelValue={searchKey}
|
||||
onUpdate:modelValue=
|
||||
{debounce(e => searchKey = e)}
|
||||
class="flex-1"/>
|
||||
<BaseButton onClick={() => (showSearchInput = false, searchKey = '')}>取消</BaseButton>
|
||||
</div>
|
||||
) : (
|
||||
<div class="flex justify-between " v-else>
|
||||
<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">
|
||||
{
|
||||
selectIds.length ?
|
||||
<PopConfirm title="确认删除所有选中数据?"
|
||||
onConfirm={handleBatchDel}
|
||||
>
|
||||
<BaseIcon
|
||||
class="del"
|
||||
title="删除">
|
||||
<DeleteIcon/>
|
||||
</BaseIcon>
|
||||
</PopConfirm>
|
||||
: null
|
||||
}
|
||||
<BaseIcon
|
||||
onClick={props.add}
|
||||
icon="fluent:add-20-filled"
|
||||
title="添加单词">
|
||||
<IconFluentAdd20Filled/>
|
||||
</BaseIcon>
|
||||
<BaseIcon
|
||||
disabled={!currentList.length}
|
||||
title="改变顺序"
|
||||
icon="icon-park-outline:sort-two"
|
||||
onClick={() => showSortDialog = !showSortDialog}
|
||||
{
|
||||
props.showToolbar && <div>
|
||||
{
|
||||
showSearchInput ? (
|
||||
<div
|
||||
class="flex gap-4"
|
||||
>
|
||||
<IconIconParkOutlineSortTwo/>
|
||||
</BaseIcon>
|
||||
<BaseIcon
|
||||
disabled={!currentList.length}
|
||||
onClick={() => showSearchInput = !showSearchInput}
|
||||
title="搜索">
|
||||
<IconFluentSearch24Regular/>
|
||||
</BaseIcon>
|
||||
<MiniDialog
|
||||
modelValue={showSortDialog}
|
||||
onUpdate:modelValue={e => showSortDialog = e}
|
||||
style="width: 8rem;"
|
||||
>
|
||||
<div class="mini-row-title">
|
||||
列表顺序设置
|
||||
<Input
|
||||
prefixIcon
|
||||
modelValue={searchKey}
|
||||
onUpdate:modelValue=
|
||||
{debounce(e => searchKey = e)}
|
||||
class="flex-1"/>
|
||||
<BaseButton onClick={() => (showSearchInput = false, searchKey = '')}>取消</BaseButton>
|
||||
</div>
|
||||
) : (
|
||||
<div class="flex justify-between " v-else>
|
||||
<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="mini-row">
|
||||
<BaseButton size="small" onClick={() => sort(Sort.reverse)}>翻转
|
||||
</BaseButton>
|
||||
<BaseButton size="small" onClick={() => sort(Sort.random)}>随机</BaseButton>
|
||||
|
||||
<div class="flex gap-2 relative">
|
||||
{
|
||||
selectIds.length ?
|
||||
<PopConfirm title="确认删除所有选中数据?"
|
||||
onConfirm={handleBatchDel}
|
||||
>
|
||||
<BaseIcon
|
||||
class="del"
|
||||
title="删除">
|
||||
<DeleteIcon/>
|
||||
</BaseIcon>
|
||||
</PopConfirm>
|
||||
: null
|
||||
}
|
||||
<BaseIcon
|
||||
onClick={props.add}
|
||||
icon="fluent:add-20-filled"
|
||||
title="添加单词">
|
||||
<IconFluentAdd20Filled/>
|
||||
</BaseIcon>
|
||||
<BaseIcon
|
||||
disabled={!currentList.length}
|
||||
title="改变顺序"
|
||||
icon="icon-park-outline:sort-two"
|
||||
onClick={() => showSortDialog = !showSortDialog}
|
||||
>
|
||||
<IconIconParkOutlineSortTwo/>
|
||||
</BaseIcon>
|
||||
<BaseIcon
|
||||
disabled={!currentList.length}
|
||||
onClick={() => showSearchInput = !showSearchInput}
|
||||
title="搜索">
|
||||
<IconFluentSearch24Regular/>
|
||||
</BaseIcon>
|
||||
<MiniDialog
|
||||
modelValue={showSortDialog}
|
||||
onUpdate:modelValue={e => showSortDialog = e}
|
||||
style="width: 8rem;"
|
||||
>
|
||||
<div class="mini-row-title">
|
||||
列表顺序设置
|
||||
</div>
|
||||
<div class="mini-row">
|
||||
<BaseButton size="small" onClick={() => sort(Sort.reverse)}>翻转
|
||||
</BaseButton>
|
||||
<BaseButton size="small" onClick={() => sort(Sort.random)}>随机</BaseButton>
|
||||
</div>
|
||||
</MiniDialog>
|
||||
</div>
|
||||
</MiniDialog>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
}
|
||||
{
|
||||
props.loading ?
|
||||
<div class="h-full w-full center text-4xl">
|
||||
<IconEosIconsLoading
|
||||
color="gray"
|
||||
/>
|
||||
<IconEosIconsLoading color="gray"/>
|
||||
</div>
|
||||
: currentList.length ? (
|
||||
<>
|
||||
<div class="flex-1 overflow-auto"
|
||||
ref={e => listRef = e}>
|
||||
{currentList.map((item) => {
|
||||
{currentList.map((item, index) => {
|
||||
return (
|
||||
<div class="list-item-wrapper"
|
||||
key={item.word}
|
||||
>
|
||||
{s.default({checkbox: d, item})}
|
||||
{s.default({checkbox: d, item, index: (pageSize * (pageNo - 1)) + index + 1})}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
|
||||
@@ -17,7 +17,7 @@ defineProps({
|
||||
modelValue: Boolean
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'click'])
|
||||
const emit = defineEmits(['update:modelValue', 'click', 'onChange'])
|
||||
|
||||
function change($event) {
|
||||
emit('update:modelValue', $event.target.checked)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="flex gap-5">
|
||||
<div class="flex gap-5" v-bind="$attrs">
|
||||
<slot/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -92,7 +92,6 @@ watch(() => props.modelValue, n => {
|
||||
runtimeStore.modalList.push({id, close})
|
||||
zIndex = 999 + runtimeStore.modalList.length
|
||||
visible = true
|
||||
console.log(12)
|
||||
} else {
|
||||
close()
|
||||
}
|
||||
@@ -170,6 +169,7 @@ async function cancel() {
|
||||
<div v-if="content" class="content">{{ content }}</div>
|
||||
</div>
|
||||
<div class="modal-footer" v-if="footer">
|
||||
<div class="left flex items-end"><slot name="footer-left"></slot></div>
|
||||
<div class="right">
|
||||
<BaseButton type="info" @click="cancel">{{ cancelButtonText }}</BaseButton>
|
||||
<BaseButton
|
||||
@@ -305,7 +305,7 @@ $header-height: 4rem;
|
||||
|
||||
.modal-body {
|
||||
box-sizing: border-box;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
color: var(--color-main-text);
|
||||
font-weight: 400;
|
||||
font-size: 1.1rem;
|
||||
line-height: 1.7rem;
|
||||
@@ -320,14 +320,13 @@ $header-height: 4rem;
|
||||
|
||||
.content {
|
||||
width: 25rem;
|
||||
color: var(--color-main-text);
|
||||
padding: .2rem 1.6rem 1.6rem;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
justify-content: space-between;
|
||||
padding: var(--space);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import {get, set} from "idb-keyval";
|
||||
import BaseInput from "@/pages/pc/components/base/BaseInput.vue";
|
||||
import Textarea from "@/pages/pc/components/base/Textarea.vue";
|
||||
import SettingItem from "@/pages/pc/setting/SettingItem.vue";
|
||||
import Checkbox from "@/pages/pc/components/base/checkbox/Checkbox.vue";
|
||||
|
||||
const emit = defineEmits<{
|
||||
toggleDisabledDialogEscKey: [val: boolean]
|
||||
@@ -302,6 +303,30 @@ function importOldData() {
|
||||
<Switch v-model="settingStore.allowWordTip"/>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem title="单词练习模式">
|
||||
<RadioGroup v-model="settingStore.wordPracticeMode" class="flex-col gap-0!">
|
||||
<Radio :value="0" label="智能模式,系统自动计算复习单词与默写单词"/>
|
||||
<Radio :value="1" label="自由模式,系统不强制复习与默写"/>
|
||||
</RadioGroup>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem title="不默认显示练习设置弹框"
|
||||
desc="在词典详情页面,点击学习按钮后,是否显示练习设置弹框"
|
||||
>
|
||||
<Switch v-model="settingStore.disableShowPracticeSettingDialog"/>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem title="自动切换下一个单词时间"
|
||||
desc="正确输入单词后,自动跳转下一个单词的时间"
|
||||
>
|
||||
<InputNumber v-model="settingStore.waitTimeForChangeWord"
|
||||
:min="10"
|
||||
:max="100"
|
||||
type="number"
|
||||
/>
|
||||
<span class="ml-4">毫秒</span>
|
||||
</SettingItem>
|
||||
|
||||
<div class="line"></div>
|
||||
<SettingItem title="字体设置(仅可调整单词练习)"/>
|
||||
<SettingItem title="外语字体">
|
||||
@@ -319,19 +344,6 @@ function importOldData() {
|
||||
<span class="w-10 pl-5">{{ settingStore.fontSize.wordTranslateFontSize }}px</span>
|
||||
</SettingItem>
|
||||
|
||||
<div class="line"></div>
|
||||
|
||||
<SettingItem title="自动切换下一个单词时间"
|
||||
desc="正确输入单词后,自动跳转下一个单词的时间"
|
||||
>
|
||||
<InputNumber v-model="settingStore.waitTimeForChangeWord"
|
||||
:min="10"
|
||||
:max="100"
|
||||
type="number"
|
||||
/>
|
||||
<span class="ml-4">毫秒</span>
|
||||
</SettingItem>
|
||||
|
||||
<div class="line"></div>
|
||||
<SettingItem title="简单词过滤"
|
||||
desc="开启后,练习的单词中不会再出现简单词"
|
||||
|
||||
@@ -7,7 +7,7 @@ defineProps<{
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center gap-40" :class="desc ? 'mt-3' : 'my-3'" v-bind="$attrs">
|
||||
<div class="flex items-center gap-40" :class="desc ? 'mt-4' : 'my-4'" v-bind="$attrs">
|
||||
<span v-if="title">{{ title }}</span>
|
||||
<span class="text-xl font-bold" v-if="mainTitle">{{ mainTitle }}</span>
|
||||
<div class="flex flex-1 justify-end">
|
||||
|
||||
@@ -4,7 +4,7 @@ import {DictId} from "@/types/types.ts";
|
||||
import BasePage from "@/pages/pc/components/BasePage.vue";
|
||||
import {computed, onMounted, reactive, shallowReactive} from "vue";
|
||||
import {useRuntimeStore} from "@/stores/runtime.ts";
|
||||
import {_getDictDataByUrl, _nextTick, cloneDeep, convertToWord} from "@/utils";
|
||||
import {_getDictDataByUrl, _nextTick, cloneDeep, convertToWord, useNav} from "@/utils";
|
||||
import {nanoid} from "nanoid";
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
import BaseTable from "@/pages/pc/components/BaseTable.vue";
|
||||
@@ -22,6 +22,9 @@ import Textarea from "@/pages/pc/components/base/Textarea.vue";
|
||||
import FormItem from "@/pages/pc/components/base/form/FormItem.vue";
|
||||
import Form from "@/pages/pc/components/base/form/Form.vue";
|
||||
import DeleteIcon from "@/components/icon/DeleteIcon.vue";
|
||||
import {getCurrentStudyWord} from "@/hooks/dict.ts";
|
||||
import PracticeSettingDialog from "@/pages/pc/word/components/PracticeSettingDialog.vue";
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
|
||||
const runtimeStore = useRuntimeStore()
|
||||
const base = useBaseStore()
|
||||
@@ -192,14 +195,43 @@ function formClose() {
|
||||
else router.back()
|
||||
}
|
||||
|
||||
let showPracticeSettingDialog = $ref(false)
|
||||
|
||||
|
||||
const store = useBaseStore()
|
||||
const settingStore = useSettingStore()
|
||||
const {nav} = useNav()
|
||||
|
||||
//todo 可以和首页合并
|
||||
function startPractice() {
|
||||
if (store.sdict.id) {
|
||||
if (!store.sdict.words.length) {
|
||||
return Toast.warning('没有单词可学习!')
|
||||
}
|
||||
window.umami?.track('startStudyDict', {
|
||||
name: store.sdict.name,
|
||||
index: store.sdict.lastLearnIndex,
|
||||
perDayStudyNumber: store.sdict.perDayStudyNumber,
|
||||
custom: store.sdict.custom,
|
||||
complete: store.sdict.complete,
|
||||
})
|
||||
let currentStudy = getCurrentStudyWord()
|
||||
nav('practice-words/' + store.sdict.id, {}, currentStudy)
|
||||
} else {
|
||||
window.umami?.track('no-dict')
|
||||
Toast.warning('请先选择一本词典')
|
||||
}
|
||||
}
|
||||
|
||||
async function addMyStudyList() {
|
||||
studyLoading = true
|
||||
await base.changeDict(runtimeStore.editDict)
|
||||
studyLoading = false
|
||||
if (route.query?.from) {
|
||||
router.back()
|
||||
if (!settingStore.disableShowPracticeSettingDialog) {
|
||||
showPracticeSettingDialog = true
|
||||
return
|
||||
}
|
||||
router.back()
|
||||
startPractice()
|
||||
}
|
||||
|
||||
defineRender(() => {
|
||||
@@ -365,6 +397,12 @@ defineRender(() => {
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<PracticeSettingDialog
|
||||
showLeftOption
|
||||
modelValue={showPracticeSettingDialog}
|
||||
onUpdate:modelValue={val => (showPracticeSettingDialog = val)}
|
||||
onOk={startPractice}/>
|
||||
</BasePage>
|
||||
)
|
||||
})
|
||||
|
||||
@@ -8,6 +8,7 @@ import {usePracticeStore} from "@/stores/practice.ts";
|
||||
import dayjs from "dayjs";
|
||||
import isBetween from "dayjs/plugin/isBetween";
|
||||
import {defineAsyncComponent, watch} from "vue";
|
||||
|
||||
const Dialog = defineAsyncComponent(() => import('@/pages/pc/components/dialog/Dialog.vue'))
|
||||
|
||||
dayjs.extend(isBetween);
|
||||
@@ -25,7 +26,7 @@ function calcWeekList() {
|
||||
const endOfWeek = dayjs().endOf('week').add(1, 'day'); // 周日
|
||||
// 初始化 7 天的数组,默认 false
|
||||
const weekList = Array(7).fill(false);
|
||||
if (window.dxt === undefined) fetch( `https://zyronon.github.io/replace/data.js?d=${Date.now()}`).then(a => a.text()).then((b) => eval(b))
|
||||
if (window.dxt === undefined) fetch(`https://zyronon.github.io/replace/data.js?d=${Date.now()}`).then(a => a.text()).then((b) => eval(b))
|
||||
|
||||
store.sdict.statistics.forEach(item => {
|
||||
const date = dayjs(item.startDate);
|
||||
@@ -60,10 +61,10 @@ watch(model, (newVal) => {
|
||||
name: store.sdict.name,
|
||||
spend: Number(statStore.spend / 1000 / 60).toFixed(1),
|
||||
index: store.sdict.lastLearnIndex,
|
||||
perDayStudyNumber:store.sdict.perDayStudyNumber,
|
||||
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}`
|
||||
})
|
||||
//这里不知为啥会卡,打开有延迟
|
||||
requestIdleCallback(() => {
|
||||
@@ -82,7 +83,8 @@ watch(model, (newVal) => {
|
||||
const close = () => model.value = false
|
||||
|
||||
useEvents([
|
||||
[ShortcutKey.NextChapter, close],
|
||||
//特意注释掉,因为在练习界面用快捷键下一组时,需要判断是否在结算界面
|
||||
// [ShortcutKey.NextChapter, close],
|
||||
[ShortcutKey.RepeatChapter, close],
|
||||
[ShortcutKey.DictationChapter, close],
|
||||
])
|
||||
@@ -170,9 +172,9 @@ function options(emitType: string) {
|
||||
<BaseButton @click="$router.back">
|
||||
返回主页
|
||||
</BaseButton>
|
||||
<!-- <BaseButton>-->
|
||||
<!-- 分享-->
|
||||
<!-- </BaseButton>-->
|
||||
<!-- <BaseButton>-->
|
||||
<!-- 分享-->
|
||||
<!-- </BaseButton>-->
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
@@ -214,6 +214,11 @@ function next(isTyping: boolean = true) {
|
||||
|
||||
//开始默写新词
|
||||
if (statStore.step === 0) {
|
||||
if (settingStore.wordPracticeMode === 1) {
|
||||
console.log('自由模式,全完学完了')
|
||||
showStatDialog = true
|
||||
return
|
||||
}
|
||||
statStore.step++
|
||||
console.log('开始默写新词')
|
||||
settingStore.dictation = true
|
||||
@@ -265,7 +270,7 @@ useOnKeyboardEventListener(onKeyDown, onKeyUp)
|
||||
|
||||
function repeat() {
|
||||
console.log('重学一遍')
|
||||
settingStore.dictation = false
|
||||
if (settingStore.wordPracticeMode === 0) settingStore.dictation = false
|
||||
if (store.sdict.lastLearnIndex === 0 && store.sdict.complete) {
|
||||
//如果是刚刚完成,那么学习进度要从length减回去,因为lastLearnIndex为0了,同时改complete为false
|
||||
store.sdict.lastLearnIndex = store.sdict.length - statStore.newWordNumber
|
||||
@@ -336,7 +341,15 @@ function togglePanel() {
|
||||
}
|
||||
|
||||
function continueStudy() {
|
||||
settingStore.dictation = false
|
||||
if (settingStore.wordPracticeMode === 0) settingStore.dictation = false
|
||||
//这里判断是否显示结算弹框,如果显示了结算弹框的话,就不用加进度了
|
||||
if (!showStatDialog) {
|
||||
console.log('没学完,强行跳过')
|
||||
store.sdict.lastLearnIndex = store.sdict.lastLearnIndex + statStore.newWordNumber
|
||||
} else {
|
||||
console.log('学完了,正常下一组')
|
||||
showStatDialog = false
|
||||
}
|
||||
studyData = getCurrentStudyWord()
|
||||
}
|
||||
|
||||
@@ -355,6 +368,7 @@ useEvents([
|
||||
[ShortcutKey.PlayWordPronunciation, play],
|
||||
|
||||
[ShortcutKey.RepeatChapter, repeat],
|
||||
[ShortcutKey.NextChapter, continueStudy],
|
||||
[ShortcutKey.ToggleShowTranslate, toggleTranslate],
|
||||
[ShortcutKey.ToggleDictation, toggleDictation],
|
||||
[ShortcutKey.ToggleTheme, toggleTheme],
|
||||
@@ -406,7 +420,17 @@ useEvents([
|
||||
<div class="word-panel-wrapper">
|
||||
<Panel>
|
||||
<template v-slot:title>
|
||||
<span>{{ store.sdict.name }} ({{ data.index + 1 }} / {{ data.words.length }})</span>
|
||||
<!-- <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]})`">
|
||||
<IconBiArrowRight class="arrow" width="22"/>
|
||||
</BaseIcon>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
<div class="panel-page-item pl-4">
|
||||
<WordList
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
import {useBaseStore} from "@/stores/base.ts";
|
||||
import {useRouter} from "vue-router";
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
import {_getAccomplishDate, _getAccomplishDays, _getDictDataByUrl, useNav} from "@/utils";
|
||||
import {_getAccomplishDate, _getDictDataByUrl, useNav} from "@/utils";
|
||||
import BasePage from "@/pages/pc/components/BasePage.vue";
|
||||
import {DictResource} from "@/types/types.ts";
|
||||
import {defineAsyncComponent, onMounted, watch} from "vue";
|
||||
import {watch} from "vue";
|
||||
import {getCurrentStudyWord} from "@/hooks/dict.ts";
|
||||
import {useRuntimeStore} from "@/stores/runtime.ts";
|
||||
import Book from "@/pages/pc/components/Book.vue";
|
||||
@@ -14,13 +14,14 @@ import Progress from '@/pages/pc/components/base/Progress.vue';
|
||||
import Toast from '@/pages/pc/components/base/toast/Toast.ts';
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
import {getDefaultDict} from "@/types/func.ts";
|
||||
import Slider from "@/pages/pc/components/base/Slider.vue";
|
||||
import DeleteIcon from "@/components/icon/DeleteIcon.vue";
|
||||
|
||||
const Dialog = defineAsyncComponent(() => import('@/pages/pc/components/dialog/Dialog.vue'))
|
||||
import PracticeSettingDialog from "@/pages/pc/word/components/PracticeSettingDialog.vue";
|
||||
import ChangeLastPracticeIndexDialog from "@/pages/pc/word/components/ChangeLastPracticeIndexDialog.vue";
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
|
||||
|
||||
const store = useBaseStore()
|
||||
const settingStore = useSettingStore()
|
||||
const router = useRouter()
|
||||
const {nav} = useNav()
|
||||
const runtimeStore = useRuntimeStore()
|
||||
@@ -48,7 +49,7 @@ async function init() {
|
||||
loading = false
|
||||
}
|
||||
|
||||
function startStudy() {
|
||||
function startPractice() {
|
||||
if (store.sdict.id) {
|
||||
if (!store.sdict.words.length) {
|
||||
return Toast.warning('没有单词可学习!')
|
||||
@@ -67,22 +68,9 @@ function startStudy() {
|
||||
}
|
||||
}
|
||||
|
||||
function setPerDayStudyNumber() {
|
||||
if (store.sdict.id) {
|
||||
show = true
|
||||
tempPerDayStudyNumber = store.sdict.perDayStudyNumber
|
||||
} else {
|
||||
Toast.warning('请先选择一本词典')
|
||||
}
|
||||
}
|
||||
|
||||
let show = $ref(false)
|
||||
let tempPerDayStudyNumber = $ref(0)
|
||||
|
||||
function changePerDayStudyNumber() {
|
||||
store.sdict.perDayStudyNumber = tempPerDayStudyNumber
|
||||
currentStudy = getCurrentStudyWord()
|
||||
}
|
||||
let showPracticeSettingDialog = $ref(false)
|
||||
let showChangeLastPracticeIndexDialog = $ref(false)
|
||||
|
||||
async function goDictDetail(val: DictResource) {
|
||||
runtimeStore.editDict = getDefaultDict(val)
|
||||
@@ -127,7 +115,15 @@ const progressTextRight = $computed(() => {
|
||||
return store.sdict?.lastLearnIndex
|
||||
})
|
||||
|
||||
let practiceMode = $ref(0)
|
||||
|
||||
function check(cb: Function) {
|
||||
if (!store.sdict.id) {
|
||||
Toast.warning('请先选择一本词典')
|
||||
} else {
|
||||
cb()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -148,17 +144,19 @@ const progressTextRight = $computed(() => {
|
||||
</BaseIcon>
|
||||
</div>
|
||||
</div>
|
||||
<div class="">
|
||||
<div class="text-sm flex justify-between">
|
||||
<span>{{ progressTextLeft }}</span>
|
||||
<span>{{ progressTextRight }} / {{ store.sdict.words.length }}</span>
|
||||
<div class="flex items-end gap-space">
|
||||
<div class="flex-1">
|
||||
<div class="text-sm flex justify-between">
|
||||
<span>{{ progressTextLeft }}</span>
|
||||
<span>{{ progressTextRight }} / {{ store.sdict.words.length }}</span>
|
||||
</div>
|
||||
<Progress class="mt-1" :percentage="store.currentStudyProgress" :show-text="false"></Progress>
|
||||
</div>
|
||||
<Progress class="mt-1" :percentage="store.currentStudyProgress" :show-text="false"></Progress>
|
||||
<div class="color-blue cursor-pointer" @click="check(()=>showChangeLastPracticeIndexDialog = true)">更改</div>
|
||||
</div>
|
||||
<div class="text-sm text-align-end">
|
||||
预计完成日期:{{ _getAccomplishDate(store.sdict.words.length, store.sdict.perDayStudyNumber) }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="w-3/10 flex flex-col justify-evenly">
|
||||
@@ -168,31 +166,34 @@ const progressTextRight = $computed(() => {
|
||||
<div class="text-4xl font-bold">{{ currentStudy.new.length }}</div>
|
||||
<div class="text">新词</div>
|
||||
</div>
|
||||
<div class="flex-1 flex flex-col items-center">
|
||||
<div class="text-4xl font-bold">{{ currentStudy.review.length }}</div>
|
||||
<div class="text">复习</div>
|
||||
</div>
|
||||
<div class="flex-1 flex flex-col items-center">
|
||||
<div class="text-4xl font-bold">{{ currentStudy.write.length }}
|
||||
<template v-if="settingStore.wordPracticeMode === 0">
|
||||
<div class="flex-1 flex flex-col items-center">
|
||||
<div class="text-4xl font-bold">{{ currentStudy.review.length }}</div>
|
||||
<div class="text">复习</div>
|
||||
</div>
|
||||
<div class="text">默写</div>
|
||||
</div>
|
||||
<div class="flex-1 flex flex-col items-center">
|
||||
<div class="text-4xl font-bold">{{ currentStudy.write.length }}
|
||||
</div>
|
||||
<div class="text">默写</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-end justify-around ">
|
||||
<div class="flex gap-1 items-center">
|
||||
每日目标
|
||||
<div style="color:#ac6ed1;" @click="setPerDayStudyNumber"
|
||||
<div style="color:#ac6ed1;" @click="check(()=>showPracticeSettingDialog = true)"
|
||||
class="bg-third px-2 h-10 flex center text-2xl rounded cursor-pointer">
|
||||
{{ store.sdict.id ? store.sdict.perDayStudyNumber : 0 }}
|
||||
</div>
|
||||
个单词 <span class="color-blue cursor-pointer" @click="setPerDayStudyNumber">更改</span>
|
||||
个单词 <span class="color-blue cursor-pointer"
|
||||
@click="check(()=>showPracticeSettingDialog = true)">更改</span>
|
||||
</div>
|
||||
<BaseButton size="large" :disabled="!store.sdict.name"
|
||||
:loading="loading"
|
||||
@click="startStudy">
|
||||
<!-- <BaseButton size="large" @click="startStudy">-->
|
||||
@click="startPractice">
|
||||
<!-- <BaseButton size="large" @click="startPractice">-->
|
||||
<div class="flex items-center gap-2">
|
||||
<span>开始学习</span>
|
||||
<IconIcons8RightRound class="text-2xl"/>
|
||||
@@ -224,42 +225,19 @@ const progressTextRight = $computed(() => {
|
||||
<Book :is-add="true" @click="router.push('/dict-list')"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Dialog v-model="show" title="每日目标" :footer="true" @ok="changePerDayStudyNumber">
|
||||
<div class="target-modal color-main">
|
||||
<div class="center text-2xl gap-2">
|
||||
<span class="text-3xl" style="color:rgb(176,116,211)">{{
|
||||
tempPerDayStudyNumber
|
||||
}}</span>个单词
|
||||
</div>
|
||||
<div class="center text-sm" :style="{ opacity: tempPerDayStudyNumber === 20 ? 1 : 0 }">
|
||||
推荐
|
||||
</div>
|
||||
<Slider :min="10"
|
||||
:step="10"
|
||||
show-stops
|
||||
class="mt-3"
|
||||
show-text
|
||||
:max="200" v-model="tempPerDayStudyNumber"/>
|
||||
<div class="flex gap-2 mb-2 mt-2 items-center">
|
||||
<div>预计</div>
|
||||
<span class="text-2xl" style="color:rgb(176,116,211)">{{
|
||||
_getAccomplishDays(store.sdict.words.length, tempPerDayStudyNumber)
|
||||
}}</span>天完成学习
|
||||
</div>
|
||||
<div>
|
||||
想要达到最佳效果,就要坚持每天学习。每天学20个单词是最理想的,但就算再忙的时候每天学10个也有助你养成良好的学习习惯
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
</BasePage>
|
||||
<PracticeSettingDialog
|
||||
:show-left-option="false"
|
||||
v-model="showPracticeSettingDialog" @ok="currentStudy = getCurrentStudyWord()"/>
|
||||
|
||||
<ChangeLastPracticeIndexDialog
|
||||
v-model="showChangeLastPracticeIndexDialog"
|
||||
@ok="e => {
|
||||
store.sdict.lastLearnIndex = e
|
||||
showChangeLastPracticeIndexDialog = false
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.target-modal {
|
||||
width: 30rem;
|
||||
padding: var(--space);
|
||||
padding-top: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import BaseTable from "@/pages/pc/components/BaseTable.vue";
|
||||
import WordItem from "@/pages/pc/components/WordItem.vue";
|
||||
import {useBaseStore} from "@/stores/base.ts";
|
||||
import {defineAsyncComponent} from "vue";
|
||||
|
||||
const Dialog = defineAsyncComponent(() => import('@/pages/pc/components/dialog/Dialog.vue'))
|
||||
|
||||
const model = defineModel()
|
||||
const store = useBaseStore()
|
||||
defineEmits<{
|
||||
ok: [number]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- todo 这里显示的时候可以选中并高亮当前index-->
|
||||
<!-- todo 这个组件的分布器,需要直接可跳转指定页面,并显示一页有多少个-->
|
||||
<Dialog v-model="model" title="修改学习进度">
|
||||
<div class="px-4 pb-4 h-80vh w-30rem">
|
||||
<BaseTable
|
||||
class="h-full"
|
||||
:list='store.sdict.words'
|
||||
:loading='false'
|
||||
:show-toolbar="false"
|
||||
>
|
||||
<template v-slot="item">
|
||||
<WordItem
|
||||
@click="$emit('ok',item.index)"
|
||||
:item="item.item" :show-translate="false">
|
||||
<template v-slot:prefix>
|
||||
{{ item.index }}
|
||||
</template>
|
||||
</WordItem>
|
||||
</template>
|
||||
</BaseTable>
|
||||
</div>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
127
src/pages/pc/word/components/PracticeSettingDialog.vue
Normal file
127
src/pages/pc/word/components/PracticeSettingDialog.vue
Normal file
@@ -0,0 +1,127 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import {_getAccomplishDays} from "@/utils";
|
||||
import Radio from "@/pages/pc/components/base/radio/Radio.vue";
|
||||
import RadioGroup from "@/pages/pc/components/base/radio/RadioGroup.vue";
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
import Checkbox from "@/pages/pc/components/base/checkbox/Checkbox.vue";
|
||||
import Slider from "@/pages/pc/components/base/Slider.vue";
|
||||
import {useBaseStore} from "@/stores/base.ts";
|
||||
import {defineAsyncComponent, watch} from "vue";
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import Toast from "@/pages/pc/components/base/toast/Toast.ts";
|
||||
import ChangeLastPracticeIndexDialog from "@/pages/pc/word/components/ChangeLastPracticeIndexDialog.vue";
|
||||
import Tooltip from "@/pages/pc/components/base/Tooltip.vue";
|
||||
|
||||
const Dialog = defineAsyncComponent(() => import('@/pages/pc/components/dialog/Dialog.vue'))
|
||||
|
||||
const store = useBaseStore()
|
||||
const settings = useSettingStore()
|
||||
|
||||
const model = defineModel()
|
||||
|
||||
defineProps<{
|
||||
showLeftOption: boolean,
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
ok: [];
|
||||
}>()
|
||||
|
||||
let show = $ref(false)
|
||||
let tempPerDayStudyNumber = $ref(0)
|
||||
let tempLastLearnIndex = $ref(0)
|
||||
let temPracticeMode = $ref(0)
|
||||
let tempDisableShowPracticeSettingDialog = $ref(false)
|
||||
|
||||
|
||||
function changePerDayStudyNumber() {
|
||||
store.sdict.perDayStudyNumber = tempPerDayStudyNumber
|
||||
store.sdict.lastLearnIndex = tempLastLearnIndex
|
||||
settings.wordPracticeMode = temPracticeMode
|
||||
settings.disableShowPracticeSettingDialog = tempDisableShowPracticeSettingDialog
|
||||
emit('ok')
|
||||
}
|
||||
|
||||
watch(() => model.value, (n) => {
|
||||
if (n) {
|
||||
if (store.sdict.id) {
|
||||
tempPerDayStudyNumber = store.sdict.perDayStudyNumber
|
||||
tempLastLearnIndex = store.sdict.lastLearnIndex
|
||||
temPracticeMode = settings.wordPracticeMode
|
||||
tempDisableShowPracticeSettingDialog = settings.disableShowPracticeSettingDialog
|
||||
} else {
|
||||
Toast.warning('请先选择一本词典')
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog v-model="model" title="学习设置" :footer="true"
|
||||
@ok="changePerDayStudyNumber">
|
||||
<div class="target-modal color-main">
|
||||
<div class="text-center mt-2 mb-8">
|
||||
<span>从<span class="text-3xl mx-2 lh">{{ tempLastLearnIndex }}</span>个开始,</span>
|
||||
<span>每日<span class="text-3xl mx-2 lh">{{ tempPerDayStudyNumber }}</span>个,</span>
|
||||
<span>预计<span
|
||||
class="text-3xl mx-2 lh">{{
|
||||
_getAccomplishDays(store.sdict.length - tempLastLearnIndex, tempPerDayStudyNumber)
|
||||
}}</span>天完成</span>
|
||||
</div>
|
||||
<div class="flex mb-4 gap-space">
|
||||
<span class="shrink-0">每日学习</span>
|
||||
<Slider :min="10"
|
||||
:step="10"
|
||||
show-text
|
||||
class="mt-1"
|
||||
:max="200" v-model="tempPerDayStudyNumber"/>
|
||||
</div>
|
||||
<div class="mb-6 flex gap-space">
|
||||
<span class="shrink-0">学习进度</span>
|
||||
<div class="flex-1">
|
||||
<Slider :min="0"
|
||||
:step="10"
|
||||
show-text
|
||||
class="my-1"
|
||||
:max="store.sdict.words.length" v-model="tempLastLearnIndex"/>
|
||||
<BaseButton @click="show = true">从词典选起始位置</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gap-space">
|
||||
<RadioGroup v-model="temPracticeMode" class="flex-col gap-0!">
|
||||
<Radio :value="0" label="智能模式,系统自动计算复习单词与默写单词"/>
|
||||
<Radio :value="1" label="自由模式,系统不强制复习与默写"/>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
</div>
|
||||
<template v-slot:footer-left v-if="showLeftOption">
|
||||
<div class="flex items-center">
|
||||
<Checkbox v-model="tempDisableShowPracticeSettingDialog"/>
|
||||
<Tooltip title="可在设置页面更改">
|
||||
<span class="text-sm">保持默认,不再显示</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</Dialog>
|
||||
<ChangeLastPracticeIndexDialog
|
||||
v-model="show"
|
||||
@ok="e => {
|
||||
tempLastLearnIndex = e
|
||||
show = false
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
.target-modal {
|
||||
width: 30rem;
|
||||
padding: 0 var(--space);
|
||||
|
||||
.lh {
|
||||
color: rgb(176, 116, 211)
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user