feat:save

This commit is contained in:
zyronon
2025-08-04 01:46:46 +08:00
parent 3fa1d34969
commit 1be66fb5cf
16 changed files with 204 additions and 537 deletions

View File

@@ -60,7 +60,7 @@
//修改element-ui的进度条底色
--el-border-color-lighter: #e2e5ed !important;
--el-border-color-lighter: #d1d5df !important;
}
.footer {
@@ -70,7 +70,6 @@
}
html.dark {
--color-primary: #0E1217;
--color-second: rgb(30, 31, 34);
--color-third: rgb(43, 45, 48);
@@ -104,7 +103,7 @@ html.dark {
--color-textarea-bg: rgb(43, 45, 48);
--color-article: white;
--el-border-color-lighter: var(--color-third) !important;
--el-border-color-lighter: rgb(73, 77, 82) !important;
.footer {
&.hide {
@@ -251,57 +250,12 @@ a {
}
}
//@supports (scrollbar-width: thin) {
// * {
// scrollbar-color: var(--color-scrollbar) #f3f4f9;
// scrollbar-width: thin;
// }
//}
footer {
$footer-height: 60rem;
box-sizing: content-box;
height: $footer-height;
display: flex;
align-items: flex-end;
justify-content: flex-end;
gap: var(--space);
}
.panel-page-item {
display: flex;
flex-direction: column;
height: 100%;
padding-bottom: var(--space);
box-sizing: border-box;
.list-header {
min-height: 3rem;
padding: .6rem var(--space);
box-sizing: border-box;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 1rem;
color: var(--color-font-3);
.left {
flex: 1;
display: flex;
align-items: center;
gap: .6rem;
}
.title {
max-width: 70%;
}
.right {
word-break: keep-all;
}
}
}
.scroll {
@@ -316,17 +270,6 @@ footer {
padding: 0 var(--space);
}
.common-list1 {
display: flex;
flex-direction: column;
width: 100%;
flex: 1;
overflow: auto;
box-sizing: border-box;
gap: 10rem;
padding: 0 var(--space);
}
.list-item-wrapper {
padding-bottom: 1rem;
}
@@ -367,7 +310,6 @@ footer {
svg {
opacity: 0;
color: var(--color-icon-hightlight);
}
&.active {
@@ -376,10 +318,6 @@ footer {
.item-sub-title {
color: var(--color-sub-text);
}
svg {
color: var(--color-icon-hightlight);
}
}
.fill {

View File

@@ -1,6 +1,5 @@
import {Article, Word} from "@/types.ts";
import {useBaseStore} from "@/stores/base.ts";
import {markRaw} from "vue";
export function useWordOptions() {
@@ -16,25 +15,22 @@ export function useWordOptions() {
store.collectWord.words.splice(rIndex, 1)
} else {
store.collectWord.words.push(val)
// store.collectWord.words = markRaw(store.collectWord.words.concat([val]))
}
store.collectWord.length = store.collectWord.words.length
}
function isWordSimple(val: Word) {
return !!store.known.words.find(v => v.word.toLowerCase() === val.word.toLowerCase())
return !!store.knownWords.includes(val.word.toLowerCase())
}
function toggleWordSimple(val: Word) {
let rIndex = store.known.words.findIndex(v => v.word.toLowerCase() === val.word.toLowerCase())
let rIndex = store.knownWords.findIndex(v => v === val.word.toLowerCase())
if (rIndex > -1) {
store.known.words.splice(rIndex, 1)
} else {
let rIndex = store.collectWord.words.findIndex(v => v.word.toLowerCase() === val.word.toLowerCase())
if (rIndex > -1) {
store.collectWord.words.splice(rIndex, 1)
}
store.known.words.push(val)
}
store.known.length = store.known.words.length
}
function delWrongWord(val: Word) {
@@ -42,6 +38,7 @@ export function useWordOptions() {
if (rIndex > -1) {
store.wrong.words.splice(rIndex, 1)
}
store.wrong.length = store.wrong.words.length
}
function delSimpleWord(val: Word) {
@@ -49,6 +46,7 @@ export function useWordOptions() {
if (rIndex > -1) {
store.known.words.splice(rIndex, 1)
}
store.known.length = store.known.words.length
}
return {
@@ -75,6 +73,7 @@ export function useArticleOptions() {
} else {
store.collectArticle.articles.push(val)
}
store.collectArticle.length = store.collectArticle.articles.length
}
return {

View File

@@ -388,7 +388,7 @@ function importData(e) {
</div>
</div>
</div>
<div class="row footer">
<div class="row">
<label class="item-title"></label>
<div class="wrapper">
<BaseButton @click="resetShortcutKeyMap">恢复默认</BaseButton>
@@ -584,10 +584,6 @@ function importData(e) {
overflow: auto;
}
.footer {
margin-bottom: 1.3rem;
}
.desc {
margin-bottom: .6rem;
font-size: .8rem;

View File

@@ -15,7 +15,9 @@ import Dialog from "@/pages/pc/components/dialog/Dialog.vue";
import Input from "@/pages/pc/components/Input.vue";
import {computed} from "vue";
import Book from "@/pages/pc/components/Book.vue";
import {ElProgress} from 'element-plus';
import {ElMessage, ElProgress} from 'element-plus';
import BaseButton from "@/components/BaseButton.vue";
import PopConfirm from "@/pages/pc/components/PopConfirm.vue";
const {nav} = useNav()
const base = useBaseStore()
@@ -26,9 +28,6 @@ let showAddChooseDialog = $ref(false)
let showSearchDialog = $ref(false)
let searchKey = $ref('')
function clickEvent(e) {
console.log('e', e)
}
async function getBookDetail(val: DictResource) {
let r = await getArticleBookDataByUrl(val)
@@ -66,6 +65,41 @@ function startStudy() {
}
router.push('/study-article')
}
let isMultiple = $ref(false)
let selectIds = $ref([])
function handleBatchDel() {
selectIds.forEach(id => {
let r = store.word.bookList.findIndex(v => v.id === id)
if (r !== -1) {
if (store.word.studyIndex === r) {
store.word.studyIndex = -1
}
if (store.word.studyIndex > r) {
store.word.studyIndex--
}
store.word.bookList.splice(r, 1)
}
})
selectIds = []
ElMessage.success("删除成功!")
}
function toggleSelect(item) {
let rIndex = selectIds.findIndex(v => v === item.id)
if (rIndex > -1) {
selectIds.splice(rIndex, 1)
} else {
selectIds.push(item.id)
}
}
async function goDictDetail(val: DictResource) {
runtimeStore.editDict = getDefaultDict(val)
nav('book-detail', {})
}
</script>
<template>
@@ -80,79 +114,44 @@ function startStudy() {
<BaseIcon @click="showSearchDialog = true"
:icon="base.currentBook.name ? 'gg:arrows-exchange':'fluent:add-20-filled'"/>
</div>
<div class="rounded-xl bg-slate-800 flex items-center py-3 px-5 text-white cursor-pointer"
:class="base.currentBook.name || 'opacity-70 cursor-not-allowed'"
@click="startStudy">
开始学习
</div>
<BaseButton
size="large"
@click="startStudy"
:disabled="!base.currentBook.name"
>
<div class="flex items-center gap-2">
<span>开始学习</span>
<Icon icon="icons8:right-round" class="text-2xl"/>
</div>
</BaseButton>
</div>
<div class="mt-5 text-sm">已学习{{ base.currentBook.lastLearnIndex }}篇文章</div>
<ElProgress class="mt-1" :percentage="base.currentBookProgress" :show-text="false"></ElProgress>
</div>
<div class="card flex flex-col">
<div class="title">
我的
</div>
<div class="grid grid-cols-6 gap-4 mt-4">
<Book :is-add="false"
quantifier="篇"
:item="item"
v-for="item in store.article.bookList"
@click="getBookDetail2(item)"/>
<Book :is-add="true"
@click="showAddChooseDialog = true"/>
</div>
</div>
<div class="flex justify-between">
<div class="title">我的书籍</div>
<div class="flex gap-4 items-center">
<PopConfirm title="确认删除所有选中书籍?" @confirm="handleBatchDel" v-if="selectIds.length">
<BaseIcon class="del" title="删除" icon="solar:trash-bin-minimalistic-linear"/>
</PopConfirm>
<div class="card">
<div class="title flex justify-between">
<span>书籍列表</span>
<BaseIcon @click="showSearchDialog = true" icon="fluent:search-24-regular"/>
</div>
<div class="grid grid-cols-6 gap-4 mt-4">
<Book :is-add="false"
quantifier="篇"
:item="item as Dict"
v-for="item in enArticle"
@click="getBookDetail(item)"/>
</div>
</div>
<Dialog v-model="showAddChooseDialog" title="选项">
<div class="color-main px-6 w-100">
<div class="cursor-pointer hover:bg-black/10 p-2 rounded"
@click="showAddChooseDialog = false,showSearchDialog = true">选择一本书籍
</div>
<p class="cursor-pointer hover:bg-black/10 p-2 rounded" @click="addBook">创建自己的书籍</p>
</div>
</Dialog>
<Dialog v-model="showSearchDialog"
:show-close="false"
@close="searchKey = ''"
:header="false">
<div class="color-main w-140">
<div class="p-4">
<Input v-if="showSearchDialog" :autofocus="true" v-model="searchKey"/>
</div>
<div class="line"></div>
<div v-if="searchList.length">
<div class="p-4 min-h-40 max-h-140 overflow-auto">
<div class="flex justify-between my-2 hover:bg-black/10 p-2 rounded"
v-for="dict in searchList"
@click="getBookDetail(dict)">
<div class="name">{{ dict.name }}</div>
<div class="">{{ dict.length }}</div>
</div>
<div class="color-blue cursor-pointer" v-if="store.article.bookList.length > 1"
@click="isMultiple = !isMultiple; selectIds = []">{{ isMultiple ? '取消' : '管理书籍' }}
</div>
</div>
<div v-else class="h-40 center flex-col text-xl color-main">
<div> 请输入书籍名称搜索</div>
<div>或直接在书籍列表选中</div>
<div class="color-blue cursor-pointer" @click="nav('dict-detail', { isAdd: true })">创建个人书籍</div>
</div>
</div>
</Dialog>
<div class="grid grid-cols-6 gap-4 mt-4">
<Book :is-add="false" quantifier="篇" :item="item" :checked="selectIds.includes(item.id)"
@check="() => toggleSelect(item)"
:show-checkbox="isMultiple && j >= 1"
v-for="(item, j) in store.article.bookList"
@click="goDictDetail(item)"/>
<Book :is-add="true" @click="router.push('/dict-list')"/>
</div>
</div>
</BasePage>
</template>

View File

@@ -183,7 +183,7 @@ useWindowClick(() => showExport = false)
</div>
<div class="footer">
<div class="import">
<BaseButton size="small">导入</BaseButton>
<BaseButton>导入</BaseButton>
<input type="file"
accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
@change="e => emit('importData',e)">
@@ -191,7 +191,7 @@ useWindowClick(() => showExport = false)
<div class="export"
style="position: relative"
@click.stop="null">
<BaseButton size="small" @click="showExport = true">导出</BaseButton>
<BaseButton @click="showExport = true">导出</BaseButton>
<MiniDialog
v-model="showExport"
style="width: 80rem;bottom: calc(100% + 10rem);top:unset;"
@@ -200,14 +200,14 @@ useWindowClick(() => showExport = false)
导出选项
</div>
<div class="mini-row">
<BaseButton size="small" @click="emit('exportData',{type:'all',data:[]})">全部文章</BaseButton>
<BaseButton @click="emit('exportData',{type:'all',data:[]})">全部文章</BaseButton>
</div>
<div class="mini-row">
<BaseButton size="small" @click="emit('exportData',{type:'chapter',data:article})">当前章节</BaseButton>
<BaseButton @click="emit('exportData',{type:'chapter',data:article})">当前章节</BaseButton>
</div>
</MiniDialog>
</div>
<BaseButton size="small" @click="add">新增</BaseButton>
<BaseButton @click="add">新增</BaseButton>
</div>
</div>
<EditArticle2

View File

@@ -459,26 +459,26 @@ let showQuestions = $ref(false)
</BaseButton>
</div>
<div class="translate-bottom mb-10" v-if="settingStore.translate">
<header class="mb-4">
<div class="text-2xl center">{{ props.article.titleTranslate }}</div>
</header>
<template v-if="getTranslateText(article).length">
<div class="text-xl mb-4 indent-8" v-for="t in getTranslateText(article)">{{ t }}</div>
</template>
</div>
<div class="center">
<BaseButton @click="showQuestions =! showQuestions">显示题目</BaseButton>
</div>
<div class="toggle" v-if="showQuestions">
<QuestionForm :questions="article.questions"
:duration="300"
:immediateFeedback="false"
:randomize="true"
/>
</div>
<template v-if="false">
<div class="translate-bottom mb-10" v-if="settingStore.translate">
<header class="mb-4">
<div class="text-2xl center">{{ props.article.titleTranslate }}</div>
</header>
<template v-if="getTranslateText(article).length">
<div class="text-xl mb-4 indent-8" v-for="t in getTranslateText(article)">{{ t }}</div>
</template>
</div>
<div class="center">
<BaseButton @click="showQuestions =! showQuestions">显示题目</BaseButton>
</div>
<div class="toggle" v-if="showQuestions">
<QuestionForm :questions="article.questions"
:duration="300"
:immediateFeedback="false"
:randomize="true"
/>
</div>
</template>
</div>
</template>
@@ -510,6 +510,7 @@ let showQuestions = $ref(false)
.titleTranslate {
@extend .title;
font-size: 1.2rem;
margin-top: 0.5rem;
font-family: var(--zh-article-family);
font-weight: bold;
}

View File

@@ -8,9 +8,6 @@ import {useBaseStore} from "@/stores/base.ts";
import EditSingleArticleModal from "@/pages/pc/article/components/EditSingleArticleModal.vue";
import {usePracticeStore} from "@/stores/practice.ts";
import {emitter, EventKey, useEvents} from "@/utils/eventBus.ts";
import IconWrapper from "@/pages/pc/components/IconWrapper.vue";
import {Icon} from "@iconify/vue";
import Tooltip from "@/pages/pc/components/Tooltip.vue";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {useSettingStore} from "@/stores/setting.ts";
import BaseIcon from "@/components/BaseIcon.vue";
@@ -19,6 +16,7 @@ import ArticleList from "@/pages/pc/components/list/ArticleList.vue";
import {useOnKeyboardEventListener} from "@/hooks/event.ts";
import {genArticleSectionData, usePlaySentenceAudio} from "@/hooks/article.ts";
import {ElProgress} from 'element-plus';
import router from "@/router.ts";
const store = useBaseStore()
const statisticsStore = usePracticeStore()
@@ -53,12 +51,13 @@ function next() {
}
function init() {
//todo 这个页面,直接访问白屏
if (!store.currentBook.articles.length) return
if (!store.currentBook?.articles?.length) {
router.push('/article')
return
}
articleData.articles = cloneDeep(store.currentBook.articles)
getCurrentPractice()
console.log('inin', articleData.article)
}
function setArticle(val: Article) {
@@ -125,8 +124,8 @@ function edit(val: Article = articleData.article) {
function wrong(word: Word) {
let lowerName = word.word.toLowerCase();
if (!store.wrong.originWords.find((v: Word) => v.word.toLowerCase() === lowerName)) {
store.wrong.originWords.push(word)
if (!store.wrong.words.find((v: Word) => v.word.toLowerCase() === lowerName)) {
store.wrong.words.push(word)
}
if (!store.knownWordsWithSimpleWords.includes(lowerName)) {
}
@@ -288,46 +287,33 @@ const {playSentenceAudio} = usePlaySentenceAudio()
<div class="panel-wrapper">
<Panel>
<template v-slot="{active}">
<div class="panel-page-item pl-4">
<div class="list-header">
<div class="left">
<div class="title">
{{ store.currentBook.name }}
</div>
<BaseIcon
:title="`下一篇(${settingStore.shortcutKeyMap[ShortcutKey.NextChapter]})`"
v-if="store.currentBook.lastLearnIndex < articleData.articles.length - 1"
@click="emitter.emit(EventKey.continueStudy)"
icon="octicon:arrow-right-24"/>
</div>
<div class="right">
{{ articleData.articles.length }}篇文章
</div>
</div>
<ArticleList
:isActive="active"
:static="false"
:show-translate="settingStore.translate"
@click="handleChangeChapterIndex"
:active-id="articleData.article.id"
:list="articleData.articles ">
<template v-slot:suffix="{item,index}">
<BaseIcon
v-if="!isArticleCollect(item)"
class="collect"
@click="toggleArticleCollect(item)"
title="收藏" icon="ph:star"/>
<BaseIcon
v-else
class="fill"
@click="toggleArticleCollect(item)"
title="取消收藏" icon="ph:star-fill"/>
</template>
</ArticleList>
</div>
<template v-slot:title>
<span>{{
store.currentBook.name
}} ({{ store.currentBook.lastLearnIndex + 1 }} / {{ articleData.articles.length }})</span>
</template>
<div class="panel-page-item pl-4">
<ArticleList
:isActive="true"
:static="false"
:show-translate="settingStore.translate"
@click="handleChangeChapterIndex"
:active-id="articleData.article.id"
:list="articleData.articles ">
<template v-slot:suffix="{item,index}">
<BaseIcon
v-if="!isArticleCollect(item)"
class="collect"
@click="toggleArticleCollect(item)"
title="收藏" icon="ph:star"/>
<BaseIcon
v-else
class="fill"
@click="toggleArticleCollect(item)"
title="取消收藏" icon="ph:star-fill"/>
</template>
</ArticleList>
</div>
</Panel>
</div>

View File

@@ -44,7 +44,7 @@ const studyProgress = $computed(() => {
<ElCheckbox v-if="showCheckbox"
:model-value="checked"
@click.stop="$emit('check')"
class="absolute left-3 bottom-2"/>
class="absolute left-0 bottom-0 h-5!"/>
<div class="custom" v-if="item.custom">自定义</div>
</template>
<div v-else class="center h-full">

View File

@@ -1,217 +1,35 @@
<script setup lang="ts">
import {useBaseStore} from "@/stores/base.ts"
import {computed, provide, watch} from "vue"
import {Dict, DictType, ShortcutKey} from "@/types.ts"
import PopConfirm from "@/pages/pc/components/PopConfirm.vue"
import BaseButton from "@/components/BaseButton.vue";
import {computed, provide} from "vue"
import {ShortcutKey} from "@/types.ts"
import {useSettingStore} from "@/stores/setting.ts";
import Close from "@/components/icon/Close.vue";
import Empty from "@/components/Empty.vue";
import {useArticleOptions, useWordOptions} from "@/hooks/dict.ts";
import Tooltip from "@/pages/pc/components/Tooltip.vue";
import BaseIcon from "@/components/BaseIcon.vue";
import {emitter, EventKey, useEvent} from "@/utils/eventBus.ts";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {useNav} from "@/utils";
import WordList from "@/pages/pc/components/list/WordList.vue";
import ArticleList from "@/pages/pc/components/list/ArticleList.vue";
import Slide from "@/pages/pc/components/Slide.vue";
const props = withDefaults(defineProps<{
type?: DictType
}>(), {
type: DictType.word
})
const store = useBaseStore()
const runtimeStore = useRuntimeStore()
const settingStore = useSettingStore()
let tabIndex = $ref(0)
provide('tabIndex', computed(() => tabIndex))
watch(() => settingStore.showPanel, n => {
if (n) {
tabIndex = 0
}
})
function changeIndex(dict: Dict) {
store.changeDict(dict)
emitter.emit(EventKey.changeDict)
}
useEvent(EventKey.changeDict, () => {
tabIndex = 0
})
const {
delWrongWord,
delSimpleWord,
toggleWordCollect,
} = useWordOptions()
const {
toggleArticleCollect
} = useArticleOptions()
const {nav} = useNav()
const showCollectToggleButton = $computed(() => {
if (props.type === DictType.word) return store.collectWord.words.length
return store.collectArticle.articles.length
})
function changeCollect() {
if (props.type === DictType.word) {
changeIndex(store.collectWord)
} else {
}
}
</script>
<template>
<Transition name="fade">
<div class="panel anim" v-show="settingStore.showPanel">
<header>
<div class="tabs">
<div class="tab" :class="tabIndex === 0 && 'active'" @click="tabIndex = 0">当前</div>
<div class="tab" :class="tabIndex === 1 && 'active'" @click="tabIndex = 1">{{ store.collectWord.name }}</div>
<div class="tab" :class="tabIndex === 2 && 'active'" @click="tabIndex = 2">{{ store.known.name }}</div>
<div class="tab" :class="tabIndex === 3 && 'active'" @click="tabIndex = 3">{{ store.wrong.name }}</div>
</div>
<header class="flex justify-between items-center py-3 px-space">
<div class="color-main"><slot name="title"></slot></div>
<Tooltip
:title="`关闭(${settingStore.shortcutKeyMap[ShortcutKey.TogglePanel]})`"
>
<Close @click="settingStore.showPanel = false"/>
</Tooltip>
</header>
<Slide :slide-count="4" :step="tabIndex">
<div class="slide-item">
<slot :active="tabIndex === 0 && settingStore.showPanel"></slot>
</div>
<div class="slide-item">
<div class="panel-page-item">
<div class="list-header">
<div class="left">
<div class="dict-name" v-if="props.type === DictType.word && store.collectWord.words.length">
{{ store.collectWord.words.length }}个单词
</div>
<div class="dict-name" v-if="props.type === DictType.article && store.collectArticle.articles.length">
{{ store.collectArticle.articles.length }}篇文章
</div>
<BaseIcon icon="fluent:add-12-regular" title="添加" @click="nav('edit-word-dict',{type:0})"/>
</div>
<template v-if="showCollectToggleButton">
<PopConfirm
:title="`确认切换?`"
@confirm="changeCollect"
>
<BaseButton size="small">切换</BaseButton>
</PopConfirm>
</template>
</div>
<template v-if="props.type === DictType.word">
<WordList
v-if="store.collectWord.words.length"
class="word-list pl-4"
:list="store.collectWord.words">
<template v-slot:suffix="{item,index}">
<BaseIcon
class="del"
@click.stop="toggleWordCollect(item)"
title="移除"
icon="solar:trash-bin-minimalistic-linear"/>
</template>
</WordList>
<Empty v-else/>
</template>
<template v-else>
<ArticleList
v-if="store.collectArticle.articles.length"
:list="store.collectArticle.articles">
<template v-slot:suffix="{item,index}">
<BaseIcon
class="del"
@click.stop="toggleArticleCollect(item)"
title="移除"
icon="solar:trash-bin-minimalistic-linear"/>
</template>
</ArticleList>
<Empty v-else/>
</template>
</div>
</div>
<div class="slide-item">
<div class="panel-page-item">
<div class="list-header">
<div class="left">
<div class="dict-name">总词数{{ store.known.words.length }}</div>
<BaseIcon icon="fluent:add-12-regular" title="添加" @click="nav('edit-word-dict',{type:2})"/>
</div>
<template v-if="store.known.words.length">
<PopConfirm
:title="`确认切换?`"
@confirm="changeIndex( store.known)"
>
<BaseButton size="small">切换</BaseButton>
</PopConfirm>
</template>
</div>
<WordList
v-if="store.known.words.length"
class="word-list pl-4"
:list="store.known.words">
<template v-slot:suffix="{item,index}">
<BaseIcon
class="del"
@click.stop="delSimpleWord(item)"
title="移除"
icon="solar:trash-bin-minimalistic-linear"/>
</template>
</WordList>
<Empty v-else/>
</div>
</div>
<div class="slide-item">
<div class="panel-page-item" v-if="store.wrong.words.length">
<div class="list-header">
<div class="dict-name">总词数{{ store.wrong.words.length }}</div>
<PopConfirm
:title="`确认切换?`"
@confirm="changeIndex( store.wrong)"
>
<BaseButton size="small">切换</BaseButton>
</PopConfirm>
</div>
<WordList
class="word-list pl-4"
:list="store.wrong.words">
<template v-slot:suffix="{item,index}">
<BaseIcon
class="del"
@click.stop="delWrongWord(item)"
title="移除"
icon="solar:trash-bin-minimalistic-linear"/>
</template>
</WordList>
</div>
<Empty v-else/>
</div>
</Slide>
<div class="flex-1 overflow-hidden">
<slot></slot>
</div>
</div>
</Transition>
</template>
<style scoped lang="scss">
$header-height: 3rem;
.slide-item {
width: var(--panel-width);
height: 100%;
}
.panel {
border-radius: .5rem;
width: var(--panel-width);
@@ -223,41 +41,5 @@ $header-height: 3rem;
z-index: 1;
border: 1px solid var(--color-item-border);
box-shadow: var(--shadow);
& > header {
min-height: 3rem;
box-sizing: border-box;
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
padding: .6rem .9rem;
border-bottom: 1px solid #e1e1e1;
gap: 1rem;
.close {
cursor: pointer;
}
.tabs {
display: flex;
align-items: center;
gap: .9rem;
font-size: .8rem;
.tab {
cursor: pointer;
word-break: keep-all;
font-size: 1rem;
transition: all .3s;
color: gray;
&.active {
color: var(--color-select-bg);
font-weight: bold;
}
}
}
}
}
</style>

View File

@@ -172,10 +172,8 @@ async function cancel() {
<div v-if="content" class="content">{{ content }}</div>
</div>
<div class="modal-footer" v-if="footer">
<div class="left">
</div>
<div class="right">
<BaseButton type="link" @click="cancel">{{ cancelButtonText }}</BaseButton>
<BaseButton type="info" @click="cancel">{{ cancelButtonText }}</BaseButton>
<BaseButton
:loading="confirmButtonLoading"
@click="ok">{{ confirmButtonText }}
@@ -331,39 +329,8 @@ $header-height: 4rem;
.modal-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding: .5rem 1.6rem;
color: #fff;
font-size: 1.1rem;
background: rgba(0, 0, 0, .2);
.left {
display: flex;
align-items: center;
height: 100%;
.text {
color: white;
font-size: 1rem;
cursor: pointer;
}
&.active {
.text {
color: white;
}
}
}
.right {
display: flex;
flex: 1;
align-items: center;
justify-content: flex-end;
height: 100%;
gap: var(--space);
}
justify-content: flex-end;
padding: var(--space);
}
}
}

View File

@@ -54,6 +54,7 @@ const {toggleTheme} = useTheme()
@click="settingStore.sideExpand = !settingStore.sideExpand"
:icon="settingStore.sideExpand?'formkit:left':'formkit:right'"/>
<BaseIcon
v-if="settingStore.sideExpand"
:title="`切换主题(${settingStore.shortcutKeyMap[ShortcutKey.ToggleTheme]})`"
@click="toggleTheme"
:icon="settingStore.theme === 'light' ? 'ep:moon' : 'tabler:sun'"/>

View File

@@ -60,7 +60,6 @@ let data = $ref<StudyData>({
wrongWords: [],
})
onMounted(() => {
let dictId = route.query.q
//如果url里有词典id那么直接请求词典数据并加到bookList里面进行学习
@@ -394,47 +393,47 @@ useEvents([
</div>
<div class="word-panel-wrapper">
<Panel>
<template v-slot="{active}">
<div class="panel-page-item pl-4"
>
<WordList
v-if="data.words.length"
:is-active="active"
: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
v-if="!isWordCollect(item)"
class="collect"
@click.stop="toggleWordCollect(item)"
title="收藏" icon="ph:star"/>
<BaseIcon
v-else
class="fill"
@click.stop="toggleWordCollect(item)"
title="取消收藏" icon="ph:star-fill"/>
<BaseIcon
v-if="!isWordSimple(item)"
class="easy"
@click.stop="toggleWordSimple(item)"
title="标记为已掌握"
icon="material-symbols:check-circle-outline-rounded"/>
<BaseIcon
v-else
class="fill"
@click.stop="toggleWordSimple(item)"
title="取消标记已掌握"
icon="material-symbols:check-circle-rounded"/>
</template>
</WordList>
<Empty v-else/>
</div>
<template v-slot:title>
<span>{{ store.sdict.name }} ({{ data.index + 1 }} / {{ data.words.length }})</span>
</template>
<div class="panel-page-item pl-4">
<WordList
v-if="data.words.length"
:is-active="true"
: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
v-if="!isWordCollect(item)"
class="collect"
@click.stop="toggleWordCollect(item)"
title="收藏" icon="ph:star"/>
<BaseIcon
v-else
class="fill"
@click.stop="toggleWordCollect(item)"
title="取消收藏" icon="ph:star-fill"/>
<BaseIcon
v-if="!isWordSimple(item)"
class="easy"
@click.stop="toggleWordSimple(item)"
title="标记为已掌握"
icon="material-symbols:check-circle-outline-rounded"/>
<BaseIcon
v-else
class="fill"
@click.stop="toggleWordSimple(item)"
title="取消标记已掌握"
icon="material-symbols:check-circle-rounded"/>
</template>
</WordList>
<Empty v-else/>
</div>
</Panel>
</div>
</div>

View File

@@ -245,7 +245,6 @@ const progressTextRight = $computed(() => {
</div>
</div>
<div class="card">
<div class="title">
已学习 <span class="text-3xl">{{ allStudyDays.length }}</span> 天
@@ -264,7 +263,7 @@ const progressTextRight = $computed(() => {
</div>
<Dialog v-model="show" title="每日目标" :footer="true" @ok="changePerDayStudyNumber">
<div class="target-modal">
<div class="target-modal color-main">
<div class="center text-2xl gap-2">
<span class="text-3xl" style="color:rgb(176,116,211)">{{
tempPerDayStudyNumber
@@ -295,7 +294,5 @@ const progressTextRight = $computed(() => {
width: 30rem;
padding: var(--space);
padding-top: 0;
color: var(--color-font-1);
}
</style>

View File

@@ -5,7 +5,7 @@ import * as localforage from "localforage";
import {nanoid} from "nanoid";
import {SAVE_DICT_KEY} from "@/utils/const.ts";
import {_getStudyProgress, checkAndUpgradeSaveDict, getDictFile} from "@/utils";
import {markRaw} from "vue";
import {markRaw, shallowReactive} from "vue";
export interface BaseState {
simpleWords: string[],
@@ -106,14 +106,14 @@ export const useBaseStore = defineStore('base', {
actions: {
setState(obj: BaseState) {
obj.word.bookList.map(book => {
book.words = markRaw(book.words)
book.articles = markRaw(book.articles)
book.statistics = markRaw(book.statistics)
book.words = shallowReactive(book.words)
book.articles = shallowReactive(book.articles)
book.statistics = shallowReactive(book.statistics)
})
obj.article.bookList.map(book => {
book.words = markRaw(book.words)
book.articles = markRaw(book.articles)
book.statistics = markRaw(book.statistics)
book.words = shallowReactive(book.words)
book.articles = shallowReactive(book.articles)
book.statistics = shallowReactive(book.statistics)
})
this.$patch(obj)
},
@@ -152,7 +152,7 @@ export const useBaseStore = defineStore('base', {
//把其他的词典的单词数据都删掉,全保存在内存里太卡了
this.word.bookList.slice(3).map(v => {
if (!v.custom) {
v.words = markRaw([])
v.words = shallowReactive([])
}
})
let rIndex = this.word.bookList.findIndex((v: Dict) => v.id === val.id)
@@ -161,10 +161,9 @@ export const useBaseStore = defineStore('base', {
}
if (rIndex > -1) {
this.word.studyIndex = rIndex
this.word.bookList[this.word.studyIndex].words = markRaw(val.words)
this.word.bookList[this.word.studyIndex].words = shallowReactive(val.words)
this.word.bookList[this.word.studyIndex].perDayStudyNumber = val.perDayStudyNumber
} else {
console.log(1)
this.word.bookList.push(getDefaultDict(val))
this.word.studyIndex = this.word.bookList.length - 1
}

View File

@@ -4,7 +4,7 @@ import jaFlag from "@/assets/img/flags/ja.png";
import deFlag from "@/assets/img/flags/de.png";
import codeFlag from "@/assets/img/flags/code.png";
import myFlag from "@/assets/img/flags/my.png";
import {markRaw} from "vue";
import {shallowReactive} from "vue";
export type Word = {
id?: string,
@@ -266,9 +266,9 @@ export function getDefaultDict(val: Partial<Dict> = {}): Dict {
custom: false,
complete: false,
...val,
words: markRaw(val.words ?? []),
articles: markRaw(val.articles ?? []),
statistics: markRaw(val.statistics ?? [])
words: shallowReactive(val.words ?? []),
articles: shallowReactive(val.articles ?? []),
statistics: shallowReactive(val.statistics ?? [])
}
}

View File

@@ -9,6 +9,9 @@ export default defineConfig({
'bg-card-active': 'bg-[var(--color-card-active)]',
'color-main': 'color-[var(--color-main-text)]',
'gap-space': 'gap-[var(--space)]',
'p-space': 'p-[var(--space)]',
'px-space': 'px-[var(--space)]',
'py-space': 'py-[var(--space)]',
},
presets: [
presetWind3(),