This commit is contained in:
Zyronon
2025-10-21 11:05:41 +00:00
parent a9b81d3164
commit 1c70908afb
7 changed files with 457 additions and 248 deletions

View File

@@ -20,6 +20,7 @@ let list = defineModel('list')
const props = withDefaults(defineProps<{
loading?: boolean
showToolbar?: boolean
showPagination?: boolean
exportLoading?: boolean
importLoading?: boolean
del?: Function
@@ -28,6 +29,7 @@ const props = withDefaults(defineProps<{
}>(), {
loading: true,
showToolbar: true,
showPagination: true,
exportLoading: false,
importLoading: false,
del: () => void 0,
@@ -71,6 +73,7 @@ let currentList = $computed(() => {
if (searchKey) {
return list.value.filter(v => v.word.includes(searchKey))
}
if (!props.showPagination) return list.value
return list.value.slice((pageNo - 1) * pageSize, (pageNo - 1) * pageSize + pageSize)
})
@@ -133,171 +136,173 @@ defineExpose({
closeImportDialog
})
defineRender(
() => {
const d = (item) => <Checkbox
modelValue={selectIds.includes(item.id)}
onChange={() => toggleSelect(item)}
size="large"/>
() => {
const d = (item) => <Checkbox
modelValue={selectIds.includes(item.id)}
onChange={() => toggleSelect(item)}
size="large"/>
return (
<div class="flex flex-col gap-3">
return (
<div class="flex flex-col gap-3">
{
props.showToolbar && <div>
{
props.showToolbar && <div>
{
showSearchInput ? (
<div class="flex gap-4">
<BaseInput
clearable
modelValue={searchKey}
onUpdate:modelValue={debounce(e => searchKey = e)}
class="flex-1">
{{
subfix: () => <IconFluentSearch24Regular
class="text-lg text-gray"
/>
}}
</BaseInput>
<BaseButton onClick={() => (showSearchInput = false, searchKey = '')}>取消</BaseButton>
</div>
) : (
<div class="flex justify-between">
<div class="flex gap-2 items-center">
<Checkbox
disabled={!currentList.length}
onChange={() => toggleSelectAll()}
modelValue={selectAll}
size="large"/>
<span>{selectIds.length} / {list.value.length}</span>
</div>
<div class="flex gap-2 relative">
{
selectIds.length ?
<PopConfirm title="确认删除所有选中数据?"
onConfirm={handleBatchDel}
>
<BaseIcon
class="del"
title="删除">
<DeleteIcon/>
</BaseIcon>
</PopConfirm>
: null
}
<BaseIcon
onClick={() => showImportDialog = true}
title="导入">
<IconSystemUiconsImport/>
</BaseIcon>
<BaseIcon
onClick={() => emit('exportData')}
title="导出">
{props.exportLoading ? <IconEosIconsLoading/> : <IconPhExportLight/>}
</BaseIcon>
<BaseIcon
onClick={props.add}
title="添加单词">
<IconFluentAdd20Regular/>
</BaseIcon>
<BaseIcon
disabled={!currentList.length}
title="改变顺序"
onClick={() => showSortDialog = !showSortDialog}
>
<IconFluentArrowSort20Regular/>
</BaseIcon>
<BaseIcon
disabled={!currentList.length}
onClick={() => showSearchInput = !showSearchInput}
title="搜索">
<IconFluentSearch20Regular/>
</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>
</div>
)
}
showSearchInput ? (
<div class="flex gap-4">
<BaseInput
clearable
modelValue={searchKey}
onUpdate:modelValue={debounce(e => searchKey = e)}
class="flex-1">
{{
subfix: () => <IconFluentSearch24Regular
class="text-lg text-gray"
/>
}}
</BaseInput>
<BaseButton onClick={() => (showSearchInput = false, searchKey = '')}>取消</BaseButton>
</div>
}
{
props.loading ?
<div class="h-full w-full center text-4xl">
<IconEosIconsLoading color="gray"/>
) : (
<div class="flex justify-between">
<div class="flex gap-2 items-center">
<Checkbox
disabled={!currentList.length}
onChange={() => toggleSelectAll()}
modelValue={selectAll}
size="large"/>
<span>{selectIds.length} / {list.value.length}</span>
</div>
: currentList.length ? (
<>
<div class="flex-1 overflow-auto"
ref={e => listRef = e}>
{currentList.map((item, index) => {
return (
<div class="list-item-wrapper"
key={item.word}
>
{s.default({checkbox: d, item, index: (pageSize * (pageNo - 1)) + index + 1})}
</div>
)
})}
</div>
<div class="flex justify-end">
<Pagination
currentPage={pageNo}
onUpdate:current-page={handlePageNo}
pageSize={pageSize}
onUpdate:page-size={(e) => pageSize = e}
pageSizes={[20, 50, 100, 200]}
layout="prev, pager, next"
total={list.value.length}/>
</div>
</>
) : <Empty/>
}
<Dialog modelValue={showImportDialog}
onUpdate:modelValue={closeImportDialog}
title="导入教程"
>
<div className="w-100 p-4 pt-0">
<div>请按照模板的格式来填写数据</div>
<div class="color-red">单词项为必填其他项可不填</div>
<div>翻译一行一个翻译前面词性后面内容如n.取消多个翻译请换行</div>
<div>例句一行原文一行译文多个请换<span class="color-red"></span></div>
<div>短语一行原文一行译文多个请换<span class="color-red"></span></div>
<div>同义词同根词词源请前往官方字典然后编辑其中某个单词参考其格式</div>
<div class="mt-6">
模板下载地址<a href="https://2study.top/libs/单词导入模板.xlsx">单词导入模板</a>
<div class="flex gap-2 relative">
{
selectIds.length ?
<PopConfirm title="确认删除所有选中数据?"
onConfirm={handleBatchDel}
>
<BaseIcon
class="del"
title="删除">
<DeleteIcon/>
</BaseIcon>
</PopConfirm>
: null
}
<BaseIcon
onClick={() => showImportDialog = true}
title="导入">
<IconSystemUiconsImport/>
</BaseIcon>
<BaseIcon
onClick={() => emit('exportData')}
title="导出">
{props.exportLoading ? <IconEosIconsLoading/> : <IconPhExportLight/>}
</BaseIcon>
<BaseIcon
onClick={props.add}
title="添加单词">
<IconFluentAdd20Regular/>
</BaseIcon>
<BaseIcon
disabled={!currentList.length}
title="改变顺序"
onClick={() => showSortDialog = !showSortDialog}
>
<IconFluentArrowSort20Regular/>
</BaseIcon>
<BaseIcon
disabled={!currentList.length}
onClick={() => showSearchInput = !showSearchInput}
title="搜索">
<IconFluentSearch20Regular/>
</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>
</div>
<div class="mt-4">
<BaseButton
onClick={() => {
let d: HTMLDivElement = document.querySelector('#upload-trigger')
d.click()
}}
loading={props.importLoading}>导入</BaseButton>
<input
id="upload-trigger"
type="file"
accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
onChange={e => emit('importData', e)}
class="w-0 h-0 opacity-0"/>
</div>
</div>
</Dialog>
)
}
</div>
)
}
}
{
props.loading ?
<div class="h-full w-full center text-4xl">
<IconEosIconsLoading color="gray"/>
</div>
: currentList.length ? (
<>
<div class="flex-1 overflow-auto"
ref={e => listRef = e}>
{currentList.map((item, index) => {
return (
<div class="list-item-wrapper"
key={item.word}
>
{s.default({checkbox: d, item, index: (pageSize * (pageNo - 1)) + index + 1})}
</div>
)
})}
</div>
{
props.showPagination && <div class="flex justify-end">
<Pagination
currentPage={pageNo}
onUpdate:current-page={handlePageNo}
pageSize={pageSize}
onUpdate:page-size={(e) => pageSize = e}
pageSizes={[20, 50, 100, 200]}
layout="prev, pager, next"
total={list.value.length}/>
</div>
}
</>
) : <Empty/>
}
<Dialog modelValue={showImportDialog}
onUpdate:modelValue={closeImportDialog}
title="导入教程"
>
<div className="w-100 p-4 pt-0">
<div>请按照模板的格式来填写数据</div>
<div class="color-red">单词项为必填其他项可不填</div>
<div>翻译一行一个翻译前面词性后面内容如n.取消多个翻译请换行</div>
<div>例句一行原文一行译文多个请换<span class="color-red"></span></div>
<div>短语一行原文一行译文多个请换<span class="color-red"></span></div>
<div>同义词同根词词源请前往官方字典然后编辑其中某个单词参考其格式</div>
<div class="mt-6">
模板下载地址<a href="https://2study.top/libs/单词导入模板.xlsx">单词导入模板</a>
</div>
<div class="mt-4">
<BaseButton
onClick={() => {
let d: HTMLDivElement = document.querySelector('#upload-trigger')
d.click()
}}
loading={props.importLoading}>导入</BaseButton>
<input
id="upload-trigger"
type="file"
accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
onChange={e => emit('importData', e)}
class="w-0 h-0 opacity-0"/>
</div>
</div>
</Dialog>
</div>
)
}
)
</script>
<style scoped lang="scss">

View File

@@ -5,7 +5,7 @@ import VolumeIcon from "@/components/icon/VolumeIcon.vue";
import {usePlayWordAudio} from "@/hooks/sound.ts";
import Tooltip from "@/components/base/Tooltip.vue";
const props = withDefaults(defineProps<{
withDefaults(defineProps<{
item: Word,
showTranslate?: boolean
showWord?: boolean
@@ -37,8 +37,8 @@ const playWordAudio = usePlayWordAudio()
<div class="item-sub-title flex flex-col gap-2" v-if="item.trans.length && showTranslate">
<div v-for="v in item.trans">
<Tooltip
v-if="v.cn.length > 30 && showTransPop"
:title="v.pos + ' ' + v.cn"
v-if="v.cn.length > 30 && showTransPop"
:title="v.pos + ' ' + v.cn"
>
<span>{{ v.pos + ' ' + v.cn.slice(0, 30) + '...' }}</span>
</Tooltip>

View File

@@ -1,17 +1,17 @@
<script setup lang="ts">
import { onMounted, provide, watch } from "vue";
import {onMounted, provide, watch} from "vue";
import Statistics from "@/pages/word/Statistics.vue";
import { emitter, EventKey, useEvents } from "@/utils/eventBus.ts";
import { useSettingStore } from "@/stores/setting.ts";
import { useRuntimeStore } from "@/stores/runtime.ts";
import { Dict, PracticeData, ShortcutKey, TaskWords, Word } from "@/types/types.ts";
import { useDisableEventListener, useOnKeyboardEventListener, useStartKeyboardEventListener } from "@/hooks/event.ts";
import {emitter, EventKey, useEvents} from "@/utils/eventBus.ts";
import {useSettingStore} from "@/stores/setting.ts";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {Dict, PracticeData, ShortcutKey, TaskWords, Word} from "@/types/types.ts";
import {useDisableEventListener, useOnKeyboardEventListener, useStartKeyboardEventListener} from "@/hooks/event.ts";
import useTheme from "@/hooks/theme.ts";
import { getCurrentStudyWord, useWordOptions } from "@/hooks/dict.ts";
import { _getDictDataByUrl, cloneDeep, resourceWrap, shuffle } from "@/utils";
import { useRoute, useRouter } from "vue-router";
import {getCurrentStudyWord, useWordOptions} from "@/hooks/dict.ts";
import {_getDictDataByUrl, cloneDeep, resourceWrap, shuffle} from "@/utils";
import {useRoute, useRouter} from "vue-router";
import Footer from "@/pages/word/components/Footer.vue";
import Panel from "@/components/Panel.vue";
import BaseIcon from "@/components/BaseIcon.vue";
@@ -19,14 +19,14 @@ import Tooltip from "@/components/base/Tooltip.vue";
import WordList from "@/components/list/WordList.vue";
import TypeWord from "@/pages/word/components/TypeWord.vue";
import Empty from "@/components/Empty.vue";
import { useBaseStore } from "@/stores/base.ts";
import { usePracticeStore } from "@/stores/practice.ts";
import {useBaseStore} from "@/stores/base.ts";
import {usePracticeStore} from "@/stores/practice.ts";
import Toast from '@/components/base/toast/Toast.ts'
import { getDefaultDict, getDefaultWord } from "@/types/func.ts";
import {getDefaultDict, getDefaultWord} from "@/types/func.ts";
import ConflictNotice from "@/components/ConflictNotice.vue";
import PracticeLayout from "@/components/PracticeLayout.vue";
import { DICT_LIST, PracticeSaveWordKey } from "@/config/env.ts";
import {DICT_LIST, PracticeSaveWordKey} from "@/config/env.ts";
const {
isWordCollect,
@@ -167,6 +167,114 @@ const nextWord: Word = $computed(() => {
function next(isTyping: boolean = true) {
// showStatDialog = true
// return
if (isTyping) statStore.inputWordNumber++
if (settingStore.wordPracticeMode === 1) {
if (data.index === data.words.length - 1) {
console.log('自由模式,全完学完了')
showStatDialog = true
localStorage.removeItem(PracticeSaveWordKey.key)
} else {
data.index++
}
} else {
if (data.index === data.words.length - 1) {
if (data.wrongWords.length) {
console.log('当前学完了,但还有错词')
data.words = shuffle(cloneDeep(data.wrongWords))
data.index = 0
data.wrongWords = []
} else {
console.log('当前学完了,没错词', statStore.total, statStore.step, data.index)
//学完了
if (statStore.step === 4) {
statStore.spend = Date.now() - statStore.startDate
console.log('全完学完了')
showStatDialog = true
localStorage.removeItem(PracticeSaveWordKey.key)
return;
// emit('complete', {})
}
//开始默认所有单词
if (statStore.step === 3) {
statStore.step++
if (taskWords.write.length) {
console.log('开始默认所有单词')
settingStore.dictation = true
data.words = shuffle(taskWords.write)
data.index = 0
} else {
console.log('开始默认所有单词-无单词略过')
return next()
}
}
//开始默写昨日
if (statStore.step === 2) {
statStore.step++
if (taskWords.review.length) {
console.log('开始默写昨日')
settingStore.dictation = true
data.words = shuffle(taskWords.review)
data.index = 0
} else {
console.log('开始默写昨日-无单词略过')
return next()
}
}
//开始复习昨日
if (statStore.step === 1) {
statStore.step++
if (taskWords.review.length) {
console.log('开始复习昨日')
settingStore.dictation = false
data.words = shuffle(taskWords.review)
data.index = 0
} else {
console.log('开始复习昨日-无单词略过')
return next()
}
}
//开始默写新词
if (statStore.step === 0) {
if (settingStore.wordPracticeMode === 1) {
console.log('自由模式,全完学完了')
showStatDialog = true
localStorage.removeItem(PracticeSaveWordKey.key)
return
}
statStore.step++
console.log('开始默写新词')
settingStore.dictation = true
data.words = shuffle(taskWords.new)
data.index = 0
}
}
} else {
let d = Math.floor(data.index / 6) - 1
if (data.index > 0 && data.index % 6 === (d < 0 ? 0 : d)) {
if (!settingStore.dictation) {
settingStore.dictation = true
data.index -= 6
} else {
settingStore.dictation = false
data.index++
}
} else {
data.index++
}
}
}
savePracticeData()
}
function next1(isTyping: boolean = true) {
// showStatDialog = true
// return
if (isTyping) statStore.inputWordNumber++
if (data.index === data.words.length - 1) {
if (data.wrongWords.length) {
console.log('当前学完了,但还有错词')
@@ -175,7 +283,6 @@ function next(isTyping: boolean = true) {
data.wrongWords = []
} else {
console.log('当前学完了,没错词', statStore.total, statStore.step, data.index)
if (isTyping) statStore.inputWordNumber++
//学完了
if (statStore.step === 4) {
@@ -246,7 +353,6 @@ function next(isTyping: boolean = true) {
}
} else {
data.index++
isTyping && statStore.inputWordNumber++
// console.log('这个词完了')
}
savePracticeData()
@@ -388,6 +494,7 @@ function randomWrite() {
data.index = 0
settingStore.dictation = true
}
function nextRandomWrite() {
console.log('继续随机默写')
initData(getCurrentStudyWord())
@@ -425,8 +532,8 @@ useEvents([
<template>
<PracticeLayout
v-loading="loading"
panelLeft="var(--word-panel-margin-left)">
v-loading="loading"
panelLeft="var(--word-panel-margin-left)">
<template v-slot:practice>
<div class="practice-word">
<div class="absolute z-1 top-4 w-full" v-if="settingStore.showNearWord">
@@ -435,7 +542,7 @@ useEvents([
v-if="prevWord">
<IconFluentArrowLeft16Regular class="arrow" width="22"/>
<Tooltip
:title="`上一个(${settingStore.shortcutKeyMap[ShortcutKey.Previous]})`"
:title="`上一个(${settingStore.shortcutKeyMap[ShortcutKey.Previous]})`"
>
<div class="word">{{ prevWord.word }}</div>
</Tooltip>
@@ -444,7 +551,7 @@ useEvents([
@click="next(false)"
v-if="nextWord">
<Tooltip
:title="`下一个(${settingStore.shortcutKeyMap[ShortcutKey.Next]})`"
:title="`下一个(${settingStore.shortcutKeyMap[ShortcutKey.Next]})`"
>
<div class="word" :class="settingStore.dictation && 'word-shadow'">{{ nextWord.word }}</div>
</Tooltip>
@@ -452,10 +559,10 @@ useEvents([
</div>
</div>
<TypeWord
ref="typingRef"
:word="word"
@wrong="onTypeWrong"
@complete="next"
ref="typingRef"
:word="word"
@wrong="onTypeWrong"
@complete="next"
/>
</div>
</template>
@@ -467,41 +574,41 @@ useEvents([
<span>{{ store.sdict.name }} ({{ store.sdict.lastLearnIndex }} / {{ store.sdict.length }})</span>
<BaseIcon
@click="continueStudy"
:title="`下一组(${settingStore.shortcutKeyMap[ShortcutKey.NextChapter]})`">
@click="continueStudy"
:title="`下一组(${settingStore.shortcutKeyMap[ShortcutKey.NextChapter]})`">
<IconFluentArrowRight16Regular class="arrow" width="22"/>
</BaseIcon>
<BaseIcon
@click="randomWrite"
:title="`随机默写(${settingStore.shortcutKeyMap[ShortcutKey.RandomWrite]})`">
@click="randomWrite"
:title="`随机默写(${settingStore.shortcutKeyMap[ShortcutKey.RandomWrite]})`">
<IconFluentArrowShuffle16Regular class="arrow" width="22"/>
</BaseIcon>
</div>
</template>
<div class="panel-page-item pl-4">
<WordList
v-if="data.words.length"
:is-active="settingStore.showPanel"
:static="false"
:show-word="!settingStore.dictation"
:show-translate="settingStore.translate"
:list="data.words"
:activeIndex="data.index"
@click="(val:any) => data.index = val.index"
v-if="data.words.length"
:is-active="settingStore.showPanel"
:static="false"
:show-word="!settingStore.dictation"
:show-translate="settingStore.translate"
:list="data.words"
:activeIndex="data.index"
@click="(val:any) => data.index = val.index"
>
<template v-slot:suffix="{item,index}">
<BaseIcon
:class="!isWordCollect(item)?'collect':'fill'"
@click.stop="toggleWordCollect(item)"
:title="!isWordCollect(item) ? '收藏' : '取消收藏'">
:class="!isWordCollect(item)?'collect':'fill'"
@click.stop="toggleWordCollect(item)"
:title="!isWordCollect(item) ? '收藏' : '取消收藏'">
<IconFluentStar16Regular v-if="!isWordCollect(item)"/>
<IconFluentStar16Filled v-else/>
</BaseIcon>
<BaseIcon
:class="!isWordSimple(item)?'collect':'fill'"
@click.stop="toggleWordSimple(item)"
:title="!isWordSimple(item) ? '标记为已掌握' : '取消标记已掌握'">
:class="!isWordSimple(item)?'collect':'fill'"
@click.stop="toggleWordSimple(item)"
:title="!isWordSimple(item) ? '标记为已掌握' : '取消标记已掌握'">
<IconFluentCheckmarkCircle16Regular v-if="!isWordSimple(item)"/>
<IconFluentCheckmarkCircle16Filled v-else/>
</BaseIcon>
@@ -513,11 +620,11 @@ useEvents([
</template>
<template v-slot:footer>
<Footer
:is-simple="isWordSimple(word)"
@toggle-simple="toggleWordSimpleWrapper"
:is-collect="isWordCollect(word)"
@toggle-collect="toggleWordCollect(word)"
@skip="next(false)"
:is-simple="isWordSimple(word)"
@toggle-simple="toggleWordSimpleWrapper"
:is-collect="isWordCollect(word)"
@toggle-collect="toggleWordCollect(word)"
@skip="next(false)"
/>
</template>
</PracticeLayout>

View File

@@ -1,27 +1,28 @@
<script setup lang="ts">
import { useBaseStore } from "@/stores/base.ts";
import { useRouter } from "vue-router";
import {useBaseStore} from "@/stores/base.ts";
import {useRouter} from "vue-router";
import BaseIcon from "@/components/BaseIcon.vue";
import { _getAccomplishDate, _getDictDataByUrl, resourceWrap, useNav } from "@/utils";
import {_getAccomplishDate, _getDictDataByUrl, resourceWrap, useNav} from "@/utils";
import BasePage from "@/components/BasePage.vue";
import { DictResource } from "@/types/types.ts";
import { watch } from "vue";
import { getCurrentStudyWord } from "@/hooks/dict.ts";
import { useRuntimeStore } from "@/stores/runtime.ts";
import {DictResource} from "@/types/types.ts";
import {watch} from "vue";
import {getCurrentStudyWord} from "@/hooks/dict.ts";
import {useRuntimeStore} from "@/stores/runtime.ts";
import Book from "@/components/Book.vue";
import PopConfirm from "@/components/PopConfirm.vue";
import Progress from '@/components/base/Progress.vue';
import Toast from '@/components/base/toast/Toast.ts';
import BaseButton from "@/components/BaseButton.vue";
import { getDefaultDict } from "@/types/func.ts";
import {getDefaultDict} from "@/types/func.ts";
import DeleteIcon from "@/components/icon/DeleteIcon.vue";
import PracticeSettingDialog from "@/pages/word/components/PracticeSettingDialog.vue";
import ChangeLastPracticeIndexDialog from "@/pages/word/components/ChangeLastPracticeIndexDialog.vue";
import { useSettingStore } from "@/stores/setting.ts";
import {useSettingStore} from "@/stores/setting.ts";
import CollectNotice from "@/components/CollectNotice.vue";
import { useFetch } from "@vueuse/core";
import { CAN_REQUEST, DICT_LIST, PracticeSaveWordKey } from "@/config/env.ts";
import { myDictList } from "@/apis";
import {useFetch} from "@vueuse/core";
import {CAN_REQUEST, DICT_LIST, PracticeSaveWordKey} from "@/config/env.ts";
import {myDictList} from "@/apis";
import PracticeWordListDialog from "@/pages/word/components/PracticeWordListDialog.vue";
const store = useBaseStore()
@@ -93,6 +94,7 @@ function startPractice() {
let showPracticeSettingDialog = $ref(false)
let showChangeLastPracticeIndexDialog = $ref(false)
let showPracticeWordListDialog = $ref(false)
async function goDictDetail(val: DictResource) {
runtimeStore.editDict = getDefaultDict(val)
@@ -182,7 +184,6 @@ const {
<BaseIcon title="切换词典"
class="ml-4"
@click="router.push('/dict-list')"
>
<IconFluentArrowSort20Regular v-if="store.sdict.name"/>
<IconFluentAdd20Filled v-else/>
@@ -198,9 +199,9 @@ const {
<Progress class="mt-1" :percentage="store.currentStudyProgress" :show-text="false"></Progress>
</div>
<PopConfirm
:disabled="!isSaveData"
title="当前存在未完成的学习任务,修改会重新生成学习任务,是否继续?"
@confirm="check(()=>showChangeLastPracticeIndexDialog = true)">
:disabled="!isSaveData"
title="当前存在未完成的学习任务,修改会重新生成学习任务,是否继续?"
@confirm="check(()=>showChangeLastPracticeIndexDialog = true)">
<div class="color-blue cursor-pointer">更改</div>
</PopConfirm>
@@ -211,21 +212,24 @@ const {
</div>
<div class="w-3/10 flex flex-col justify-evenly">
<div class="center text-xl">{{ isSaveData ? '上次学习任务' : '今日任务' }}</div>
<div class="center gap-2">
<span class="text-xl">{{ isSaveData ? '上次学习任务' : '今日任务' }}</span>
<span class="color-blue cursor-pointer" @click="showPracticeWordListDialog = true">词表</span>
</div>
<div class="flex">
<div class="flex-1 flex flex-col items-center">
<div class="text-4xl font-bold">{{ currentStudy.new.length }}</div>
<div class="text">新词</div>
<div class="text">新词</div>
</div>
<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 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 class="text">默写单词</div>
</div>
</template>
</div>
@@ -240,9 +244,9 @@ const {
</div>
个单词
<PopConfirm
:disabled="!isSaveData"
title="当前存在未完成的学习任务,修改会重新生成学习任务,是否继续?"
@confirm="check(()=>showPracticeSettingDialog = true)">
:disabled="!isSaveData"
title="当前存在未完成的学习任务,修改会重新生成学习任务,是否继续?"
@confirm="check(()=>showPracticeSettingDialog = true)">
<span class="color-blue cursor-pointer">更改</span>
</PopConfirm>
</div>
@@ -299,13 +303,18 @@ const {
</BasePage>
<PracticeSettingDialog
:show-left-option="false"
v-model="showPracticeSettingDialog"
@ok="savePracticeSetting"/>
:show-left-option="false"
v-model="showPracticeSettingDialog"
@ok="savePracticeSetting"/>
<ChangeLastPracticeIndexDialog
v-model="showChangeLastPracticeIndexDialog"
@ok="saveLastPracticeIndex"
v-model="showChangeLastPracticeIndexDialog"
@ok="saveLastPracticeIndex"
/>
<PracticeWordListDialog
:data="currentStudy"
v-model="showPracticeWordListDialog"
/>
<CollectNotice/>

View File

@@ -77,7 +77,7 @@ const progress = $computed(() => {
<div class="flex justify-between items-center">
<div class="stat">
<div class="row">
<div class="num">{{ `${practiceData.index}/${practiceData.words.length}` }}</div>
<div class="num">{{ `${practiceData.index + 1}/${practiceData.words.length}` }}</div>
<div class="line"></div>
<div class="name">{{ status }}</div>
</div>

View File

@@ -12,7 +12,7 @@ import {useSettingStore} from "@/stores/setting.ts";
import Toast from "@/components/base/toast/Toast.ts";
import ChangeLastPracticeIndexDialog from "@/pages/word/components/ChangeLastPracticeIndexDialog.vue";
import Tooltip from "@/components/base/Tooltip.vue";
import { useRuntimeStore } from "@/stores/runtime.ts";
import {useRuntimeStore} from "@/stores/runtime.ts";
const Dialog = defineAsyncComponent(() => import('@/components/dialog/Dialog.vue'))
@@ -63,11 +63,24 @@ watch(() => model.value, (n) => {
<Dialog v-model="model" title="学习设置" :footer="true"
@ok="changePerDayStudyNumber">
<div class="target-modal color-main">
<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-2 mb-8">
<span><span class="text-3xl mx-2 lh">{{ tempLastLearnIndex }}</span>个开始</span>
<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">{{
class="text-3xl mx-2 lh">{{
_getAccomplishDays(runtimeStore.editDict.length - tempLastLearnIndex, tempPerDayStudyNumber)
}}</span>天完成</span>
</div>
@@ -90,13 +103,6 @@ watch(() => model.value, (n) => {
<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">
@@ -108,8 +114,8 @@ watch(() => model.value, (n) => {
</template>
</Dialog>
<ChangeLastPracticeIndexDialog
v-model="show"
@ok="e => {
v-model="show"
@ok="e => {
tempLastLearnIndex = e
show = false
}"
@@ -125,5 +131,13 @@ watch(() => model.value, (n) => {
.lh {
color: rgb(176, 116, 211)
}
.mode-item{
@apply w-50% border border-blue border-solid p-2 rounded-lg cursor-pointer;
}
.active{
@apply bg-blue color-white;
}
}
</style>

View File

@@ -0,0 +1,74 @@
<script setup lang="ts">
import BaseTable from "@/components/BaseTable.vue";
import WordItem from "@/components/WordItem.vue";
import {defineAsyncComponent} from "vue";
import {TaskWords} from "@/types/types.ts";
import Checkbox from "@/components/base/checkbox/Checkbox.vue";
const Dialog = defineAsyncComponent(() => import('@/components/dialog/Dialog.vue'))
const model = defineModel()
defineProps<{
data: TaskWords
}>()
let showTranslate = $ref(false)
</script>
<template>
<Dialog v-model="model" title="任务">
<div class="px-4 pb-4 h-80vh flex gap-4">
<div class="h-full flex flex-col gap-2">
<div class="flex justify-between items-center">
<span class="title">新词</span>
</div>
<BaseTable
class="overflow-auto flex-1 w-85"
:list='data.new'
:loading='false'
:show-toolbar="false"
:showPagination="false"
>
<template v-slot="item">
<WordItem
:item="item.item"
:show-translate="showTranslate">
<template v-slot:prefix>
{{ item.index }}
</template>
</WordItem>
</template>
</BaseTable>
</div>
<div class="h-full flex flex-col gap-2">
<div class="flex justify-between items-center">
<span class="title">复习单词</span>
<Checkbox v-model="showTranslate">翻译</Checkbox>
</div>
<BaseTable
class="overflow-auto flex-1 w-85"
:list='data.review'
:loading='false'
:show-toolbar="false"
:showPagination="false"
>
<template v-slot="item">
<WordItem
:item="item.item"
:show-translate="showTranslate">
<template v-slot:prefix>
{{ item.index }}
</template>
</WordItem>
</template>
</BaseTable>
</div>
</div>
</Dialog>
</template>
<style scoped lang="scss">
</style>