Chapters of toolbar are switchable

This commit is contained in:
zyronon
2023-11-12 23:59:05 +08:00
parent 60e3975b3e
commit 4bf40d7780
13 changed files with 217 additions and 127 deletions

1
components.d.ts vendored
View File

@@ -16,6 +16,7 @@ declare module 'vue' {
BaseIcon: typeof import('./src/components/BaseIcon.vue')['default']
ChapterDetail: typeof import('./src/components/ChapterDetail.vue')['default']
ChapterList: typeof import('./src/components/list/ChapterList.vue')['default']
ChapterName: typeof import('./src/components/Toolbar/ChapterName.vue')['default']
Close: typeof import('./src/components/icon/Close.vue')['default']
CommonWordList: typeof import('./src/components/list/CommonWordList.vue')['default']
DictGroup: typeof import('./src/components/Toolbar/DictGroup.vue')['default']

View File

@@ -32,7 +32,7 @@
--toolbar-width: 700rem;
--toolbar-height: 60rem;
--panel-width: 400rem;
--space: 24rem;
--space: 20rem;
--panel-margin-left: calc(50% - var(--practice-wrapper-padding-right) / 2 + var(--toolbar-width) / 2 + 24rem);
}
@@ -268,7 +268,7 @@ footer {
justify-content: space-between;
transition: all .3s;
padding: 10rem;
gap: 20rem;
gap: 10rem;
border: 1px solid var(--color-item-border);
.left {

View File

@@ -19,7 +19,6 @@ import VirtualWordList from "@/components/list/VirtualWordList.vue";
import Modal from "@/components/Modal/Modal.vue";
import {emitter, EventKey} from "@/utils/eventBus.ts";
// useDisableEventListener()
const store = useBaseStore()
const settingStore = useSettingStore()
@@ -33,7 +32,7 @@ let step = $ref(1)
let isAddDict = $ref(false)
let wordList = $ref([])
let dictList = $computed(() => {
let dictList: Dict[] = $computed(() => {
return [
store.collect,
store.simple,
@@ -85,11 +84,11 @@ onMounted(() => {
}
})
console.log('categoryList', categoryList)
console.log('tagList', tagList)
// console.log('categoryList', categoryList)
// console.log('tagList', tagList)
})
function selectDict(val: { dict: Dict, index: number }) {
function selectDict(val: { index: number }) {
store.current.editIndex = val.index
wordList = cloneDeep(store.editDict.originWords)
isAddDict = false
@@ -237,13 +236,20 @@ watch(() => step, v => {
let show = $ref(false)
useDisableEventListener(() => show)
function close() {
show = false
}
onMounted(() => {
emitter.on(EventKey.editDict, (dict: Dict) => {
show = true
let rIndex = dictList.findIndex(v => v.id === dict.id)
if (rIndex > -1) {
selectDict({index: rIndex})
addWord()
show = true
}
})
})
</script>
@@ -429,14 +435,8 @@ onMounted(() => {
@import "@/assets/css/variable";
#AddWordDialog {
position: fixed;
width: 600rem;
width: 650rem;
height: 70vh;
left: 50%;
top: 50%;
transform: translate3D(-50%, -50%, 0);
z-index: 9999999;
background: var(--color-second-bg);
transition: all .3s;
&.add-word-mode {
@@ -447,6 +447,10 @@ onMounted(() => {
grid-template-columns: repeat(2, 1fr);
display: none;
}
.dict{
padding-right: var(--space);
}
}
$header-height: 60rem;
@@ -492,7 +496,6 @@ onMounted(() => {
background: var(--color-second-bg);
color: var(--color-font-1);
padding-left: var(--space);
padding-right: var(--space);
padding-bottom: var(--space);
box-sizing: border-box;
overflow: auto;
@@ -530,7 +533,7 @@ onMounted(() => {
}
.list-wrapper {
width: 350rem;
width: 400rem;
display: flex;
flex-direction: column;
font-size: 14rem;

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import {dictionaryResources} from '@/assets/dictionary.ts'
import {useBaseStore} from "@/stores/base.ts"
import {onUnmounted, watch} from "vue"
import {watch} from "vue"
import {DefaultDict, Dict, DictResource, DictType, languageCategoryOptions, Sort} from "@/types.ts"
import {chunk, cloneDeep, groupBy, reverse, shuffle} from "lodash-es";
import {$computed, $ref} from "vue/macros";
@@ -19,18 +19,6 @@ import {useRuntimeStore} from "@/stores/runtime.ts";
import {useSettingStore} from "@/stores/setting.ts";
import {emitter, EventKey} from "@/utils/eventBus.ts";
interface IProps {
modelValue?: boolean,
}
const props = withDefaults(defineProps<IProps>(), {
modelValue: true,
})
const emit = defineEmits<{
close: []
}>()
const baseStore = useBaseStore()
const settingStore = useSettingStore()
const runtimeStore = useRuntimeStore()
@@ -40,52 +28,51 @@ let groupByLanguage = groupBy(dictionaryResources, 'language')
let translateLanguageList = $ref([])
let step = $ref(1)
let loading = $ref(false)
watch(() => props.modelValue, (n: boolean) => {
let find = baseStore.myDicts.find((v: Dict) => v.name === baseStore.currentDict.name)
if (find) {
runtimeStore.editDict = cloneDeep(find)
}
watch(() => runtimeStore.showDictModal, (n: boolean) => {
runtimeStore.editDict = cloneDeep(baseStore.currentDict)
})
async function selectDict(item: DictResource) {
console.log('item', item)
step = 1
loading = true
let find: Dict = baseStore.myDicts.find((v: Dict) => v.name === item.name)
if (find) {
runtimeStore.editDict = cloneDeep(find)
if (find.type === DictType.article) {
if (!find.articles.length) {
let r = await fetch(`./dicts/${find.language}/${find.type}/${find.translateLanguage}/${find.url}`)
let v = await r.json()
find.articles = v.map(s => {
runtimeStore.editDict.articles = v.map(s => {
s.id = uuidv4()
return s
})
}
runtimeStore.editDict = cloneDeep(find)
}
loading = false
} else {
let data: Dict = {
...cloneDeep(DefaultDict),
...item,
}
runtimeStore.editDict = cloneDeep(data)
let r = await fetch(`./dicts/${data.language}/${data.type}/${data.translateLanguage}/${item.url}`)
r.json().then(v => {
console.log('v', v)
if (data.type === DictType.article) {
data.articles = cloneDeep(v.map(s => {
runtimeStore.editDict.articles = cloneDeep(v.map(s => {
s.id = uuidv4()
return s
}))
runtimeStore.editDict = cloneDeep(data)
} else {
data.originWords = v
data.words = v
data.chapterWords = chunk(v, data.chapterWordNumber)
runtimeStore.editDict = cloneDeep(data)
runtimeStore.editDict.originWords = v
runtimeStore.editDict.words = v
runtimeStore.editDict.chapterWords = chunk(v, runtimeStore.editDict.chapterWordNumber)
console.log(' runtimeStore.editDict', runtimeStore.editDict)
}
loading = false
})
}
}
@@ -96,10 +83,9 @@ function changeDict() {
}
function close() {
emit('close')
runtimeStore.showDictModal = false
}
function groupByDictTags(dictList: DictResource[]) {
return dictList.reduce<Record<string, DictResource[]>>((result, dict) => {
dict.tags.forEach((tag) => {
@@ -169,17 +155,13 @@ function changeSort(v) {
resetChapterList()
}
onUnmounted(() => {
close()
})
</script>
<template>
<Modal
:header="false"
:model-value="props.modelValue"
:show-close="false"
@close="close">
v-model="runtimeStore.showDictModal"
:show-close="false">
<div class="slide">
<div class="slide-list" :class="`step${step}`">
<div class="dict-page">
@@ -327,7 +309,7 @@ onUnmounted(() => {
</div>
</div>
</div>
<div class="other">
<div class="other" v-loading="loading">
<div class="common-title">
<template v-if="dictIsArticle">
文章列表{{ runtimeStore.editDict.articles.length }}

View File

@@ -242,7 +242,7 @@ $header-height: 60rem;
.window {
//width: 75vw;
//height: 70vh;
box-shadow: var(--color-main-bg) 0 0 10rem 1rem;
box-shadow: var(--color-main-bg) 0 0 6rem 0;
border-radius: $radius;
animation: bounce-in $time ease-out;

View File

@@ -1,21 +1,17 @@
<script setup lang="ts">
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
import Modal from "@/components/Modal/Modal.vue";
import {$ref} from "vue/macros";
import {onMounted, onUnmounted, watch} from "vue";
import {usePlayWordAudio} from "@/hooks/sound.ts";
import {emitter, EventKey} from "@/utils/eventBus.ts";
import ListItem from "@/components/list/ListItem.vue";
import {Word} from "@/types.ts";
import {useRuntimeStore} from "@/stores/runtime.ts";
import VirtualWordList from "@/components/list/VirtualWordList.vue";
let show = $ref(false)
let loading = $ref(false)
let list = $ref([])
let title = $ref('')
let progress = $ref(0)
const playWordAudio = usePlayWordAudio()
const runtimeStore = useRuntimeStore()
onMounted(() => {
@@ -81,26 +77,10 @@ onUnmounted(() => {
:indeterminate="false"
:show-text="false"/>
</div>
<virtual-list class="virtual-list"
:keeps="20"
data-key="name"
:data-sources="list"
:estimate-size="85"
item-class="dict-virtual-item"
>
<template #={source}>
<ListItem
class="common-list-item"
:show-volume="true">
<div class="item-title">
<span class="word">{{ source.name }}</span>
<span class="phonetic">{{ source.usphone }}</span>
<VolumeIcon class="volume" @click="playWordAudio(source.name)"></VolumeIcon>
</div>
<div class="item-sub-title" v-if="source.trans.length">{{ source.trans.join('') }}</div>
</ListItem>
</template>
</virtual-list>
<VirtualWordList
class="word-list"
:list="list">
</VirtualWordList>
</div>
</Modal>
</template>
@@ -109,7 +89,7 @@ onUnmounted(() => {
@import "@/assets/css/style";
.all-word {
padding: var(--space);
padding-bottom: var(--space);
padding-top: 0;
width: 400rem;
height: 75vh;
@@ -128,17 +108,3 @@ onUnmounted(() => {
}
}
</style>
<style lang="scss">
@import "@/assets/css/variable";
.virtual-list {
overflow: auto;
height: 100%;
}
.dict-virtual-item {
margin-bottom: 15rem;
}
</style>

View File

@@ -17,6 +17,7 @@ import Tooltip from "@/components/Tooltip.vue";
import IconWrapper from "@/components/IconWrapper.vue";
import CommonWordList from "@/components/list/CommonWordList.vue";
import BaseIcon from "@/components/BaseIcon.vue";
import {emitter, EventKey} from "@/utils/eventBus.ts";
const store = useBaseStore()
const settingStore = useSettingStore()
@@ -80,7 +81,7 @@ const {
</el-radio-group>
<Tooltip title="添加">
<IconWrapper>
<Icon icon="fluent:add-12-regular"/>
<Icon icon="fluent:add-12-regular" @click="emitter.emit(EventKey.editDict,store.collect)"/>
</IconWrapper>
</Tooltip>
<div class="dict-name" v-if="practiceType === DictType.word && store.collect.words.length">

View File

@@ -112,10 +112,16 @@ function togglePanel() {
settingStore.showPanel = !settingStore.showPanel
}
function jumpSpecifiedChapter(val: number) {
store.currentDict.chapterIndex = val
repeat()
}
onMounted(() => {
emitter.on(EventKey.next, next)
emitter.on(EventKey.write, write)
emitter.on(EventKey.repeat, repeat)
emitter.on(EventKey.jumpSpecifiedChapter, jumpSpecifiedChapter)
emitter.on(ShortcutKey.NextChapter, next)
emitter.on(ShortcutKey.PreviousChapter, prev)
@@ -135,6 +141,7 @@ onUnmounted(() => {
emitter.off(EventKey.next, next)
emitter.off(EventKey.write, write)
emitter.off(EventKey.repeat, repeat)
emitter.off(EventKey.jumpSpecifiedChapter, jumpSpecifiedChapter)
emitter.off(ShortcutKey.NextChapter, next)
emitter.off(ShortcutKey.PreviousChapter, prev)
@@ -159,7 +166,7 @@ onUnmounted(() => {
<Footer/>
</div>
<AddWordDialog></AddWordDialog>
<DictModal :model-value="runtimeStore.showDictModal" @close="runtimeStore.showDictModal = false"/>
<DictModal/>
<SettingModal v-if="runtimeStore.showSettingModal" @close="runtimeStore.showSettingModal = false"/>
<Statistics/>
</template>

View File

@@ -0,0 +1,108 @@
<script setup lang="ts">
import {useBaseStore} from "@/stores/base.ts";
import MiniModal from "@/components/Modal/MiniModal.vue";
import {useWindowClick} from "@/hooks/event.ts";
import {emitter, EventKey} from "@/utils/eventBus.ts";
import {nextTick, watch} from "vue";
const store = useBaseStore()
let timer = 0
let show = $ref(false)
useWindowClick(() => show = false)
function toggle(val) {
clearTimeout(timer)
if (val) {
emitter.emit(EventKey.closeOther)
show = val
} else {
timer = setTimeout(() => {
show = val
}, 100)
}
}
function clickJumpSpecifiedChapter(index: number) {
emitter.emit(EventKey.jumpSpecifiedChapter, index)
}
const listRef: HTMLElement = $ref(null as any)
watch(() => show, n => {
if (n){
nextTick(()=>{
listRef?.children[store.currentDict.chapterIndex]?.scrollIntoView({block: 'center'})
})
}
})
</script>
<template>
<div class="ChapterName" @click.stop="null">
<div class="info hvr-grow"
@mouseenter="toggle(true)"
@mouseleave="toggle(false)"
>
{{ store.chapterName }}
</div>
<MiniModal
v-model="show"
@mouseenter="toggle(true)"
@mouseleave="toggle(false)"
style="width: 230rem;"
>
<div class="chapter-list" ref="listRef">
<div class="chapter-list-item"
:class="store.currentDict.chapterIndex === index && 'active'"
v-for="(item,index) in store.currentDict.chapterWords"
@click="clickJumpSpecifiedChapter(index)">
<input type="radio" :checked="store.currentDict.chapterIndex === index">
<div class="title">{{ index + 1 }}&nbsp;&nbsp;&nbsp;{{ item.length }}</div>
</div>
</div>
</MiniModal>
</div>
</template>
<style scoped lang="scss">
.ChapterName {
position: relative;
}
.chapter-list {
max-height: 250rem;
overflow: auto;
padding-right: 5rem;
.chapter-list-item {
margin-bottom: 7rem;
height: 36rem;
display: flex;
align-items: center;
gap: 10rem;
width: 100%;
box-sizing: border-box;
background: var(--color-item-bg);
color: var(--color-font-1);
font-size: 18rem;
border-radius: 8rem;
transition: all .3s;
padding: 10rem;
border: 1px solid var(--color-item-border);
&:hover {
background: var(--color-item-hover);
}
&.active {
background: var(--color-item-active);
}
}
}
.el-radio-group {
display: flex;
flex-direction: column;
align-items: flex-start;
}
</style>

View File

@@ -16,6 +16,7 @@ import {usePracticeStore} from "@/stores/practice.ts";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {$ref} from "vue/macros";
import {ShortcutKey} from "@/types.ts";
import ChapterName from "@/components/Toolbar/ChapterName.vue";
const {toggleTheme} = useTheme()
const store = useBaseStore()
@@ -60,12 +61,18 @@ watch(() => store.load, n => {
<template>
<header ref="headerRef">
<div class="content">
<Tooltip
:title="`词典详情(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.OpenDictDetail]})`">
<div class="info hvr-grow" @click="runtimeStore.showDictModal = true">
{{ store.dictTitle }} {{ practiceStore.repeatNumber ? ' 复习错词' : '' }}
<div class="dict-name">
<Tooltip
:title="`词典详情(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.OpenDictDetail]})`">
<div class="info hvr-grow" @click="runtimeStore.showDictModal = true">
{{ store.currentDict.name }} {{ practiceStore.repeatNumber ? ' 复习错词' : '' }}
</div>
</Tooltip>
<ChapterName/>
<div class="info-text" v-if="practiceStore.repeatNumber">
复习错词
</div>
</Tooltip>
</div>
<div class="options" ref="moreOptionsRef">
<div class="more" :class="settingStore.collapse && 'hide'">
@@ -147,6 +154,32 @@ watch(() => store.load, n => {
<FeedbackModal v-if="showFeedbackModal" @close="showFeedbackModal = false"/>
</template>
<style lang="scss">
.info {
border-radius: 6rem;
color: var(--color-font-1);
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
transition: all .3s;
padding: 6rem 8rem;
&:hover {
background: var(--color-main-active);
color: white;
}
}
.info-text {
@extend .info;
cursor: unset;
&:hover {
background: unset;
}
}
</style>
<style scoped lang="scss">
@import "@/assets/css/variable";
@@ -180,22 +213,11 @@ header {
align-items: center;
justify-content: space-between;
.info {
font-size: 17rem;
padding: 6rem 10rem;
border-radius: 6rem;
color: var(--color-font-1);
.dict-name {
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
transition: all .3s;
max-width: 45%;
&:hover {
background: var(--color-main-active);
color: white;
}
font-size: 17rem;
position: relative;
}
.hide {

View File

@@ -53,10 +53,9 @@ export function useStartKeyboardEventListener() {
const settingStore = useSettingStore()
useEventListener('keydown', (e: KeyboardEvent) => {
e.preventDefault()
// console.log('e',e.keyCode,e.code)
if (!runtimeStore.disableEventListener) {
e.preventDefault()
let shortcutKey = getShortcutKey(e)
// console.log('shortcutKey', shortcutKey)
@@ -117,7 +116,7 @@ export function useOnKeyboardEventListener(onKeyDown: (e: KeyboardEvent) => void
export function useDisableEventListener(watchVal?: any) {
const runtimeStore = useRuntimeStore()
watch(() => watchVal, n => {
watch(watchVal, n => {
if (n) {
runtimeStore.disableEventListener = true
} else {
@@ -125,12 +124,12 @@ export function useDisableEventListener(watchVal?: any) {
}
})
onMounted(() => {
if (!watchVal) {
if (watchVal === undefined) {
runtimeStore.disableEventListener = true
}
})
onUnmounted(() => {
if (!watchVal) {
if (watchVal === undefined) {
runtimeStore.disableEventListener = false
}
})

View File

@@ -194,18 +194,18 @@ export const useBaseStore = defineStore('base', {
},
dictTitle(state: State) {
let title = this.currentDict.name
return title + this.chapterName
},
chapterName(state: State) {
let title = ''
switch (state.current.dictType) {
case DictType.collect:
if (state.current.dictType === DictType.collect) {
title += ` ${this.currentDict.chapterIndex + 1}`
if (state.current.practiceType === DictType.word) {
return `${this.currentDict.chapterIndex + 1}`
}
break
case DictType.word:
case DictType.article:
case DictType.customWord:
case DictType.customArticle:
title += `${this.currentDict.chapterIndex + 1}`
break
return `${this.currentDict.chapterIndex + 1}`
}
return title
}

View File

@@ -15,4 +15,5 @@ export const EventKey = {
write: 'write',
editDict: 'editDict',
openMyDictDialog: 'openMyDictDialog',
jumpSpecifiedChapter: 'jumpSpecifiedChapter',
}