This commit is contained in:
zyronon
2023-12-10 16:40:26 +08:00
parent 48094eae91
commit 9a283cfdca
8 changed files with 505 additions and 56 deletions

View File

@@ -59,7 +59,7 @@ watch(store.wrong.originWords, (n) => {
})
async function init() {
console.time()
// console.time()
store.init().then(() => {
store.load = true
// console.timeEnd()
@@ -74,11 +74,9 @@ onMounted(() => {
if (/Mobi|Android|iPhone/i.test(navigator.userAgent)) {
// 当前设备是移动设备
console.log('当前设备是移动设备')
router.replace('/mobile')
// router.replace('/mobile')
}
})
</script>
<template>

View File

@@ -37,7 +37,7 @@ const style = $computed(() => {
width: 100%;
height: 100%;
display: flex;
transition: all .5s;
transition: all .3s;
}
:deep(.page) {

View File

@@ -15,7 +15,7 @@ import Setting from "@/components/Setting.vue";
import {useRuntimeStore} from "@/stores/runtime.ts";
const runtimeStore = useRuntimeStore()
let disabledDialogEscKey = $ref(false)
let disabledDialogEscKey = $ref(true)
</script>
<template>

View File

@@ -106,41 +106,26 @@ watch(() => store.load, n => {
<!-- <Add/>-->
<Tooltip title="添加">
<IconWrapper>
<Icon icon="ic:outline-cloud-upload"
@click="emitter.emit(EventKey.openDictModal,'my')"
/>
</IconWrapper>
</Tooltip>
<BaseIcon
@click="emitter.emit(EventKey.openDictModal,'my')"
title="添加"
icon="ic:outline-cloud-upload"/>
<BaseIcon
@click="showFeedbackModal = true"
title="反馈"
icon="ph:bug-beetle"/>
</div>
<div class="with-bg anim">
<Tooltip
<BaseIcon
@click="runtimeStore.showSettingModal = true"
:title="`设置(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.OpenSetting]})`"
>
<IconWrapper>
<Icon icon="uil:setting" @click="runtimeStore.showSettingModal = true"/>
</IconWrapper>
</Tooltip>
<!-- <div class="base-button" @click="emitter.emit(EventKey.openStatModal)">ok</div>-->
<Tooltip
icon="uil:setting"/>
<BaseIcon
@click="settingStore.showPanel = !settingStore.showPanel"
:title="`单词本(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.TogglePanel]})`"
>
<IconWrapper>
<Icon icon="tdesign:menu-unfold" class="menu" @click="settingStore.showPanel = !settingStore.showPanel"/>
</IconWrapper>
</Tooltip>
icon="tdesign:menu-unfold"/>
</div>
</div>
</div>

View File

@@ -0,0 +1,252 @@
<script setup lang="ts">
import {useBaseStore} from "@/stores/base.ts"
import {$computed, $ref} from "vue/macros"
import {computed, onMounted, onUnmounted, provide, watch} from "vue"
import {Dict, DictType, ShortcutKey} from "@/types.ts"
import PopConfirm from "@/components/PopConfirm.vue"
import BaseButton from "@/components/BaseButton.vue";
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 {Icon} from "@iconify/vue";
import Tooltip from "@/components/Tooltip.vue";
import IconWrapper from "@/components/IconWrapper.vue";
import BaseIcon from "@/components/BaseIcon.vue";
import {emitter, EventKey} from "@/utils/eventBus.ts";
import {useRouter} from "vue-router";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {cloneDeep} from "lodash-es";
import WordList from "@/components/list/WordList.vue";
import ArticleList from "@/components/list/ArticleList.vue";
import Slide from "@/components/Slide.vue";
const router = useRouter()
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
}
})
let practiceType = $ref(DictType.word)
function changeIndex(dict: Dict) {
store.changeDict(dict, practiceType)
}
onMounted(() => {
emitter.on(EventKey.changeDict, () => {
tabIndex = 0
})
})
onUnmounted(() => {
emitter.off(EventKey.changeDict)
})
const {
delWrongWord,
delSimpleWord,
toggleWordCollect,
} = useWordOptions()
const {
toggleArticleCollect
} = useArticleOptions()
function addCollect() {
runtimeStore.editDict = cloneDeep(store.collect)
router.push({path: '/dict', query: {type: 'addWordOrArticle'}})
}
function addSimple() {
runtimeStore.editDict = cloneDeep(store.simple)
router.push({path: '/dict', query: {type: 'addWordOrArticle'}})
}
const showCollectToggleButton = $computed(() => {
if (store.currentDict.type === DictType.collect) {
if (store.current.practiceType !== practiceType) {
return (practiceType === DictType.word && store.collect.words.length) ||
(practiceType === DictType.article && store.collect.articles.length)
}
} else {
return (practiceType === DictType.word && store.collect.words.length) ||
(practiceType === DictType.article && store.collect.articles.length)
}
return false
})
</script>
<template>
<div class="panel anim">
<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.collect.name }}</div>
<div class="tab" :class="tabIndex === 2 && 'active'" @click="tabIndex = 2">{{ store.simple.name }}</div>
<div class="tab" :class="tabIndex === 3 && 'active'" @click="tabIndex = 3">{{ store.wrong.name }}</div>
</div>
</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">总词数{{ store.collect.words.length }}</div>
<BaseIcon icon="fluent:add-12-regular" title="添加" @click="addCollect"/>
</div>
<template v-if="showCollectToggleButton">
<PopConfirm
:title="`确认切换?`"
@confirm="changeIndex( store.collect)"
>
<BaseButton size="small">切换</BaseButton>
</PopConfirm>
</template>
</div>
<WordList
v-if="store.collect.words.length"
class="word-list"
:list="store.collect.words">
<template v-slot:suffix="{item,index}">
<BaseIcon
class="del"
@click="toggleWordCollect(item)"
title="移除"
icon="solar:trash-bin-minimalistic-linear"/>
</template>
</WordList>
</div>
</div>
<div class="slide-item">
<div class="panel-page-item">
<div class="list-header">
<div class="left">
<div class="dict-name">总词数{{ store.simple.words.length }}</div>
<BaseIcon icon="fluent:add-12-regular" title="添加" @click="addSimple"/>
</div>
<template v-if="store.currentDict.type !== DictType.simple && store.simple.words.length">
<PopConfirm
:title="`确认切换?`"
@confirm="changeIndex( store.simple)"
>
<BaseButton size="small">切换</BaseButton>
</PopConfirm>
</template>
</div>
<WordList
v-if="store.simple.words.length"
class="word-list"
:list="store.simple.words">
<template v-slot:suffix="{item,index}">
<BaseIcon
class="del"
@click="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>
<template
v-if="store.currentDict.type !== DictType.wrong && store.wrong.words.length">
<PopConfirm
:title="`确认切换?`"
@confirm="changeIndex( store.wrong)"
>
<BaseButton size="small">切换</BaseButton>
</PopConfirm>
</template>
</div>
<WordList
class="word-list"
:list="store.wrong.words">
<template v-slot:suffix="{item,index}">
<BaseIcon
class="del"
@click="delWrongWord(item)"
title="移除"
icon="solar:trash-bin-minimalistic-linear"/>
</template>
</WordList>
</div>
<Empty v-else/>
</div>
</Slide>
</div>
</template>
<style scoped lang="scss">
@import "@/assets/css/variable";
$header-height: 50rem;
.slide-item {
width: 25%;
height: 100%;
}
.panel {
border-radius: 8rem;
width: 100%;
background: var(--color-second-bg);
height: 100%;
display: flex;
flex-direction: column;
transition: all .3s;
z-index: 1;
border: 1px solid var(--color-item-border);
box-shadow: var(--shadow);
& > header {
min-height: 50rem;
box-sizing: border-box;
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
padding: 10rem 15rem;
border-bottom: 1px solid #e1e1e1;
gap: 15rem;
.close {
cursor: pointer;
}
.tabs {
display: flex;
align-items: center;
gap: 15rem;
font-size: 14rem;
.tab {
cursor: pointer;
word-break: keep-all;
font-size: 16rem;
transition: all .3s;
color: gray;
&.active {
color: var(--color-main-active);
font-weight: bold;
}
}
}
}
}
</style>

View File

@@ -4,11 +4,18 @@ import {useBaseStore} from "@/stores/base.ts";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {useSettingStore} from "@/stores/setting.ts";
import {$computed, $ref} from "vue/macros";
import {Word} from "@/types.ts";
import {ShortcutKey, Sort, Word} from "@/types.ts";
import {cloneDeep} from "lodash-es";
import {emitter, EventKey} from "@/utils/eventBus.ts";
import {syncMyDictList} from "@/hooks/dict.ts";
import {onMounted, onUnmounted} from "vue";
import {syncMyDictList, useWordOptions} from "@/hooks/dict.ts";
import {onMounted, onUnmounted, watch} from "vue";
import BaseButton from "@/components/BaseButton.vue";
import Options from "@/pages/practice/Options.vue";
import BaseIcon from "@/components/BaseIcon.vue";
import MobilePanel from "@/pages/mobile/components/MobilePanel.vue";
import MiniDialog from "@/components/dialog/MiniDialog.vue";
import WordList from "@/components/list/WordList.vue";
import Empty from "@/components/Empty.vue";
const store = useBaseStore()
const runtimeStore = useRuntimeStore()
@@ -29,7 +36,6 @@ const word: Word = $computed(() => {
}
})
function getCurrentPractice() {
if (store.chapter.length) {
wordData.words = store.chapter
@@ -61,25 +67,150 @@ function next() {
getCurrentPractice()
}
watch(() => store.load, n => {
getCurrentPractice()
})
onMounted(() => {
getCurrentPractice()
})
onUnmounted(() => {
})
const {
isWordCollect,
toggleWordCollect,
isWordSimple,
toggleWordSimple,
toggleWordSimpleWrapper
} = useWordOptions()
let showSortOption = $ref(false)
let showPanel = $ref(false)
</script>
<template>
<div id="mobile">
<div class="content">
<div class="translate">
<div class="translate-item" v-for="(v,i) in word.trans">
<span>{{ v }}</span>
<div class="slide">
<div class="slide-list" :class="{showPanel}">
<div class="practice" @click.stop="showPanel = false">
<div class="tool-bar">
<BaseIcon
v-if="!isWordCollect(word)"
class="collect"
@click="toggleWordCollect(word)"
icon="ph:star"/>
<BaseIcon
v-else
class="fill"
@click="toggleWordCollect(word)"
icon="ph:star-fill"/>
<BaseIcon
@click="showPanel = !showPanel"
icon="tdesign:menu-unfold"/>
</div>
<div class="word-content">
<div class="translate">
<div class="translate-item" v-for="(v,i) in word.trans">
<span>{{ v }}</span>
</div>
</div>
<div class="word">
{{ word.name }}
</div>
<div class="phonetic" v-if="settingStore.wordSoundType === 'us' && word.usphone">[{{ word.usphone }}]</div>
<div class="phonetic" v-if="settingStore.wordSoundType === 'uk' && word.ukphone">[{{ word.ukphone }}]</div>
</div>
<div class="options">
<div class="wrapper">
<BaseButton>不认识</BaseButton>
<BaseButton @click="wordData.index++">认识</BaseButton>
</div>
</div>
</div>
<div class="list">
<MobilePanel>
<template v-slot="{active}">
<div class="panel-page-item"
v-loading="!store.load"
>
<div class="list-header">
<div class="left">
<div class="title">
{{ store.chapterName }}
</div>
<BaseIcon title="切换词典"
@click="emitter.emit(EventKey.openDictModal,'list')"
icon="carbon:change-catalog"/>
<div style="position:relative;"
@click.stop="null">
<BaseIcon
title="改变顺序"
icon="icon-park-outline:sort-two"
@click="showSortOption = !showSortOption"
/>
<MiniDialog
v-model="showSortOption"
style="width: 130rem;"
>
<div class="mini-row-title">
列表循环设置
</div>
<div class="mini-row">
<BaseButton size="small" @click="sort(Sort.reverse)">翻转</BaseButton>
<BaseButton size="small" @click="sort(Sort.random)">随机</BaseButton>
</div>
</MiniDialog>
</div>
<BaseIcon icon="bi:arrow-right"
@click="next"
v-if="store.currentDict.chapterIndex < store.currentDict.chapterWords.length - 1"/>
</div>
<div class="right">
{{ wordData.words.length }}个单词
</div>
</div>
<WordList
v-if="wordData.words.length"
:is-active="active"
:static="true"
:show-word="!settingStore.dictation"
:show-translate="settingStore.translate"
:list="wordData.words"
:activeIndex="wordData.index"
@click="(val:any) => wordData.index = val.index"
>
<template v-slot:suffix="{item,index}">
<BaseIcon
v-if="!isWordCollect(item)"
class="collect"
@click="toggleWordCollect(item)"
title="收藏" icon="ph:star"/>
<BaseIcon
v-else
class="fill"
@click="toggleWordCollect(item)"
title="取消收藏" icon="ph:star-fill"/>
<BaseIcon
v-if="!isWordSimple(item)"
class="easy"
@click="toggleWordSimple(item)"
title="标记为简单词"
icon="material-symbols:check-circle-outline-rounded"/>
<BaseIcon
v-else
class="fill"
@click="toggleWordSimple(item)"
title="取消标记简单词"
icon="material-symbols:check-circle-rounded"/>
</template>
</WordList>
<Empty v-else/>
</div>
</template>
</MobilePanel>
</div>
</div>
<div class="word">
{{ word.name }}
</div>
</div>
</div>
@@ -91,6 +222,92 @@ onUnmounted(() => {
z-index: 1;
font-size: 14rem;
color: black;
width: 100%;
height: 100%;
$list-width: 75vw;
.slide {
width: 100%;
height: 100%;
overflow: hidden;
.slide-list {
width: calc(100% + $list-width);
height: 100%;
display: flex;
transition: all .5s;
}
.showPanel{
transform: translateX(-$list-width);
}
}
.practice {
width: 100vw;
height: 100%;
display: flex;
flex-direction: column;
gap: 10rem;
.tool-bar {
height: 50rem;
display: flex;
padding: 0 10rem;
align-items: center;
justify-content: flex-end;
gap: 10rem;
}
.word-content {
width: 100%;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.translate {
width: 80%;
font-size: 18rem;
.translate-item {
display: flex;
align-items: center;
gap: 10rem;
}
}
.word {
font-size: 26rem;
}
.phonetic {
font-size: 16rem;
}
}
.options {
display: flex;
align-items: center;
justify-content: center;
padding-bottom: 20rem;
.wrapper {
width: 80%;
display: flex;
flex-direction: column;
gap: 10rem;
}
.base-button {
width: 100%;
}
}
}
.list {
width: $list-width;
}
}
</style>

View File

@@ -315,13 +315,10 @@ onUnmounted(() => {
</div>
</MiniDialog>
</div>
<Tooltip
:title="`下一章(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.NextChapter]})`"
v-if="store.currentDict.chapterIndex < store.currentDict.chapterWords.length - 1">
<IconWrapper>
<Icon @click="emitter.emit(EventKey.next)" icon="octicon:arrow-right-24"/>
</IconWrapper>
</Tooltip>
<BaseIcon icon="bi:arrow-right"
@click="emitter.emit(EventKey.next)"
:title="`下一章(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.NextChapter]})`"
v-if="store.currentDict.chapterIndex < store.currentDict.chapterWords.length - 1"/>
</div>
<div class="right">
{{ data.words.length }}个单词

View File

@@ -5,16 +5,16 @@ import Mobile from '@/pages/mobile/index.vue'
import Test from "@/pages/test.vue";
const routes: any[] = [
{path: '/practice', component: Practice},
{path: '/dict', name: 'dict', component: Dict},
{path: '/mobile', name: 'dict', component: Mobile},
{path: '/test', name: 'test', component: Test},
{path: '/', redirect: '/practice'},
{path: '/practice', component: Practice},
{path: '/dict', component: Dict},
{path: '/mobile', component: Mobile},
{path: '/test', component: Test},
{path: '/', redirect: '/practice'},
]
const router = VueRouter.createRouter({
history: VueRouter.createWebHashHistory(),
routes,
history: VueRouter.createWebHashHistory(),
routes,
})
export default router