save
This commit is contained in:
17
src/App.vue
17
src/App.vue
@@ -53,18 +53,19 @@ onUnmounted(() => {
|
||||
window.removeEventListener('keyup', onKeyUp)
|
||||
})
|
||||
|
||||
// watch(store.$state, (n) => {
|
||||
// localStorage.setItem(SaveKey, JSON.stringify(n))
|
||||
// })
|
||||
watch(store.$state, (n) => {
|
||||
localStorage.setItem(SaveKey, JSON.stringify(n))
|
||||
})
|
||||
|
||||
function next() {
|
||||
if (store.wordIndex === store.chapter.length - 1) {
|
||||
if (store.chapterIndex !== store.currentDict.chapterList.length - 1) {
|
||||
if (store.currentDict.wordIndex === store.chapter.length - 1) {
|
||||
if (store.currentDict.chapterIndex !== store.currentDict.chapterList.length - 1) {
|
||||
store.currentDict.wordIndex = 0
|
||||
store.currentDict.chapterIndex++
|
||||
console.log('这一章节完了')
|
||||
} else {
|
||||
console.log('这本书完了')
|
||||
return
|
||||
}
|
||||
} else {
|
||||
store.currentDict.wordIndex++
|
||||
@@ -140,8 +141,6 @@ async function onKeyDown(e: KeyboardEvent) {
|
||||
|
||||
const [playAudio] = usePlayWordAudio()
|
||||
|
||||
const showDictModal = $ref(false)
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -171,7 +170,7 @@ const showDictModal = $ref(false)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <Side/>-->
|
||||
<Side/>
|
||||
<DictModal/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -180,7 +179,7 @@ const showDictModal = $ref(false)
|
||||
@import "@/assets/css/colors";
|
||||
|
||||
.main-page {
|
||||
background: $dark-bg;
|
||||
background: $dark-main-bg;
|
||||
width: 100vw;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
$dark-bg: rgb(46, 46, 46);
|
||||
$dark-bg2: rgb(72, 72, 72);
|
||||
$dark-main-bg: rgb(46, 46, 46);
|
||||
$dark-second-bg: rgb(60, 63, 65);
|
||||
|
||||
$font-color: rgb(187, 187, 187);
|
||||
|
||||
$main: rgb(12, 140, 233);
|
||||
$second: #7B91CB;
|
||||
$space: 20rem;
|
||||
$second: rgb(75, 110, 175);
|
||||
$item-hover: rgb(75, 75, 75);
|
||||
$space: 20rem;
|
||||
$footer-height: 40rem;
|
||||
@@ -6,6 +6,7 @@ html, body {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: $font-color;
|
||||
}
|
||||
|
||||
#app {
|
||||
@@ -28,6 +29,15 @@ html, body {
|
||||
border-radius: 10rem;
|
||||
}
|
||||
|
||||
footer {
|
||||
box-sizing: content-box;
|
||||
height: $footer-height;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: flex-end;
|
||||
gap: $space;
|
||||
}
|
||||
|
||||
.my-button {
|
||||
font-size: 13rem;
|
||||
cursor: pointer;
|
||||
|
||||
@@ -20,7 +20,7 @@ const isActive = computed(() => {
|
||||
<arrow-left @click="back" theme="outline" size="20" fill="#929596" :strokeWidth="2"/>
|
||||
<div class="dict-name">16.</div>
|
||||
</header>
|
||||
<WordList :active="isActive" :word-list="store.chapter" :index="store.wordIndex"></WordList>
|
||||
<WordList :isActive="isActive" :word-list="store.chapter" :activeIndex="store.wordIndex"></WordList>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,47 +1,51 @@
|
||||
<script setup lang="ts">
|
||||
import {ArrowLeft} from '@icon-park/vue-next'
|
||||
import {inject} from "vue"
|
||||
|
||||
const back = inject('back')
|
||||
const next = inject('next')
|
||||
import {Word} from "@/types.ts"
|
||||
|
||||
const props = defineProps<{
|
||||
list: Word[],
|
||||
activeIndex: number
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['update:activeIndex'])
|
||||
|
||||
function next() {
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="chapter-wrapper page">
|
||||
<header>
|
||||
<arrow-left @click="back" theme="outline" size="20" fill="#929596" :strokeWidth="2"/>
|
||||
<div class="dict-name">CET-4</div>
|
||||
</header>
|
||||
<div class="chapter-list">
|
||||
<div class="chapter-item" v-for="i in 10" @click="next">
|
||||
<div class="title">1.A private conversation</div>
|
||||
</div>
|
||||
<div class="list">
|
||||
<div class="item" :class="activeIndex === index && 'active'"
|
||||
v-for="(item,index) in list" @click="$emit('update:activeIndex', index)">
|
||||
<div class="title">{{ index + 1 }}.</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.chapter-wrapper {
|
||||
header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10rem;
|
||||
@import "@/assets/css/colors";
|
||||
|
||||
.list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15rem;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0 $space;
|
||||
overflow: auto;
|
||||
box-sizing: border-box;
|
||||
|
||||
.item {
|
||||
cursor: pointer;
|
||||
margin-bottom: 10rem;
|
||||
padding: 10rem;
|
||||
border-radius: 10rem;
|
||||
border: 1px solid gray;
|
||||
|
||||
.dict-name {
|
||||
font-size: 26rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.chapter-list {
|
||||
.chapter-item {
|
||||
cursor: pointer;
|
||||
margin-bottom: 10rem;
|
||||
padding: 10rem;
|
||||
border-radius: 10rem;
|
||||
border: 1px solid gray;
|
||||
&.active {
|
||||
background: $second;
|
||||
border: 1px solid $second;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,14 @@ import {Dict, Word} from "@/types.ts"
|
||||
import {chunk} from "lodash";
|
||||
import {$computed, $ref} from "vue/macros";
|
||||
import WordList from "@/components/WordList.vue";
|
||||
import ChapterList from "@/components/ChapterList.vue"
|
||||
|
||||
const store = useBaseStore()
|
||||
let currentSelectDict: Dict = $ref({name: '新概念英语-2'} as any)
|
||||
let currentSelectDict: Dict = $ref({
|
||||
name: '新概念英语-2',
|
||||
chapterList: [],
|
||||
chapterIndex: -1
|
||||
} as any)
|
||||
let step = $ref(1)
|
||||
|
||||
const currentSelectChapter: Word[] = $computed(() => {
|
||||
@@ -73,18 +78,18 @@ async function selectDict(item: Dict) {
|
||||
<div class="desc">{{ i.description }}</div>
|
||||
<div class="num">{{ i.length }}词</div>
|
||||
<arrow-right v-if="currentSelectDict.name === i.name"
|
||||
@click="step = 1"
|
||||
@click.stop="step = 1"
|
||||
class="go" theme="outline" size="20" fill="#ffffff"
|
||||
:strokeWidth="2"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chapter-wrapper">
|
||||
<div class="chapter-list">
|
||||
<div class="chapter-item" v-for="(item,index) in currentSelectDict.chapterList">
|
||||
<div class="title">{{ index }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<ChapterList
|
||||
class="chapter-list"
|
||||
:list="currentSelectDict.chapterList"
|
||||
v-model:active-index="currentSelectDict.chapterIndex"
|
||||
/>
|
||||
<div class="footer">
|
||||
<div class="my-button">确定</div>
|
||||
</div>
|
||||
@@ -113,20 +118,13 @@ async function selectDict(item: Dict) {
|
||||
<div class="num">{{ currentSelectDict.length }}词</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chapter-wrapper">
|
||||
<div class="chapter-list">
|
||||
<div class="chapter-item"
|
||||
@click="currentSelectDict.chapterIndex = index"
|
||||
v-for="(item,index) in currentSelectDict.chapterList">
|
||||
<div class="title">{{ index }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<ChapterList :list="currentSelectDict.chapterList"
|
||||
v-model:active-index="currentSelectDict.chapterIndex"
|
||||
/>
|
||||
</div>
|
||||
<div class="other">
|
||||
<div class="word-list">
|
||||
<WordList :word-list="currentSelectChapter" :index="0" :active="false"/>
|
||||
</div>
|
||||
<WordList class="word-list" :list="currentSelectChapter" :activeIndex="-1" :isActive="false"/>
|
||||
<div class="footer">
|
||||
<div class="my-button">返回</div>
|
||||
<div class="my-button">确定</div>
|
||||
@@ -153,6 +151,7 @@ $header-height: 60rem;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -161,7 +160,7 @@ $header-height: 60rem;
|
||||
overflow: hidden;
|
||||
|
||||
&.show {
|
||||
z-index: 0;
|
||||
z-index: 999;
|
||||
|
||||
.modal-mask {
|
||||
opacity: 1;
|
||||
@@ -210,8 +209,8 @@ $header-height: 60rem;
|
||||
|
||||
.modal {
|
||||
position: relative;
|
||||
background: $dark-bg2;
|
||||
box-shadow: $dark-bg2 0 0 10rem 1rem;
|
||||
background: $dark-second-bg;
|
||||
box-shadow: $dark-second-bg 0 0 10rem 1rem;
|
||||
opacity: 0;
|
||||
transition: transform $time, opacity $time;
|
||||
width: 75vw;
|
||||
@@ -291,8 +290,8 @@ $header-height: 60rem;
|
||||
cursor: pointer;
|
||||
padding: 10rem;
|
||||
border-radius: 10rem;
|
||||
background: $dark-bg;
|
||||
border: 1px solid $dark-bg;
|
||||
background: $dark-main-bg;
|
||||
border: 1px solid $dark-main-bg;
|
||||
position: relative;
|
||||
|
||||
.go {
|
||||
@@ -307,23 +306,13 @@ $header-height: 60rem;
|
||||
}
|
||||
}
|
||||
|
||||
$footer-height: 50rem;
|
||||
$footer-height: 40rem;
|
||||
|
||||
.chapter-wrapper {
|
||||
min-width: 25%;
|
||||
|
||||
.chapter-list {
|
||||
padding: 0 $space;
|
||||
height: calc(100% - $footer-height);
|
||||
overflow: auto;
|
||||
|
||||
.chapter-item {
|
||||
cursor: pointer;
|
||||
margin-bottom: 10rem;
|
||||
padding: 10rem;
|
||||
border-radius: 10rem;
|
||||
border: 1px solid gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,7 +320,7 @@ $footer-height: 50rem;
|
||||
box-sizing: content-box;
|
||||
height: $footer-height;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: flex-end;
|
||||
justify-content: flex-end;
|
||||
gap: $space;
|
||||
}
|
||||
@@ -442,13 +431,8 @@ $footer-height: 50rem;
|
||||
.other {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
padding-left: $space;
|
||||
|
||||
.word-list {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding-right: $space;
|
||||
overflow: auto;
|
||||
height: calc(100% - $footer-height);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,47 +1,35 @@
|
||||
<script setup lang="ts">
|
||||
import {useBaseStore} from "@/stores/base.ts"
|
||||
import WordList from "@/components/WordList.vue"
|
||||
import {ArrowRight, MenuFold} from '@icon-park/vue-next'
|
||||
import {ArrowLeft, ArrowRight, MenuFold} from '@icon-park/vue-next'
|
||||
|
||||
import {$ref} from "vue/macros"
|
||||
import DictList from "@/components/DictList.vue"
|
||||
import ChapterList from "@/components/ChapterList.vue"
|
||||
import {computed, onMounted, provide} from "vue"
|
||||
import ChapterDetail from "@/components/ChapterDetail.vue"
|
||||
import {computed, provide} from "vue"
|
||||
import {Swiper, SwiperSlide} from 'swiper/vue';
|
||||
import 'swiper/css';
|
||||
import {Swiper as SwiperClass} from "swiper/types"
|
||||
import {Dict, DictType} from "@/types.ts"
|
||||
|
||||
const store = useBaseStore()
|
||||
|
||||
const swiperIns0: SwiperClass = $ref(null as any)
|
||||
const swiperIns1: SwiperClass = $ref(null as any)
|
||||
|
||||
onMounted(() => {
|
||||
})
|
||||
|
||||
let tabIndex = $ref(0)
|
||||
let stepIndex = $ref(0)
|
||||
provide('tabIndex', computed(() => tabIndex))
|
||||
|
||||
function slideTo(index: number) {
|
||||
swiperIns0.slideTo(index)
|
||||
tabIndex = index
|
||||
swiperIns0.slideTo(tabIndex = index)
|
||||
}
|
||||
|
||||
|
||||
function next() {
|
||||
swiperIns1.slideNext()
|
||||
function changeDict(dict: Dict, i: number) {
|
||||
if (store.currentDictType.name !== dict.type) {
|
||||
store.currentDictType = {
|
||||
name: dict.type,
|
||||
dictUrl: dict.url
|
||||
}
|
||||
}
|
||||
store.currentDict.wordIndex = i
|
||||
}
|
||||
|
||||
function back() {
|
||||
swiperIns1.slidePrev()
|
||||
}
|
||||
|
||||
provide('next', next)
|
||||
provide('back', back)
|
||||
provide('tabIndex', computed(() => tabIndex))
|
||||
provide('stepIndex', computed(() => stepIndex))
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div class="side" :class="store.sideIsOpen && 'open'">
|
||||
@@ -58,25 +46,57 @@ provide('stepIndex', computed(() => stepIndex))
|
||||
<div class="side-content">
|
||||
<swiper @swiper="e=>swiperIns0 = e" class="mySwiper" :allow-touch-move="false">
|
||||
<swiper-slide>
|
||||
<swiper @swiper="e=>swiperIns1 = e"
|
||||
@activeIndexChange="e=>stepIndex = e.activeIndex"
|
||||
class="mySwiper" :allow-touch-move="false">
|
||||
<swiper-slide>
|
||||
<DictList/>
|
||||
</swiper-slide>
|
||||
<swiper-slide>
|
||||
<ChapterList/>
|
||||
</swiper-slide>
|
||||
<swiper-slide>
|
||||
<ChapterDetail/>
|
||||
</swiper-slide>
|
||||
</swiper>
|
||||
<div class="page0">
|
||||
<header>
|
||||
<arrow-left theme="outline" size="20" fill="#929596" :strokeWidth="2"/>
|
||||
<div class="dict-name">16.</div>
|
||||
</header>
|
||||
<WordList
|
||||
class="word-list"
|
||||
:isActive="store.sideIsOpen && tabIndex === 0"
|
||||
:list="store.chapter"
|
||||
:activeIndex="store.dict.wordIndex"/>
|
||||
<footer v-if="[DictType.custom,DictType.inner].includes(store.currentDictType.name)">
|
||||
<div class="my-button" @click="store.changeDict(store.dict,store.dict.chapterIndex,store.dict.wordIndex)">
|
||||
切换
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</swiper-slide>
|
||||
<swiper-slide>
|
||||
<WordList :active="store.sideIsOpen && tabIndex === 1" class="page" :word-list="store.newWords" :index="0"/>
|
||||
<div class="page0">
|
||||
<header>
|
||||
<div class="dict-name">总词数:{{ store.newWordDict.wordList.length }}</div>
|
||||
</header>
|
||||
<WordList
|
||||
class="word-list"
|
||||
:isActive="store.sideIsOpen && tabIndex === 1"
|
||||
:list="store.newWordDict.wordList"
|
||||
:activeIndex="store.newWordDict.wordIndex"/>
|
||||
<footer v-if="store.currentDictType.name !== DictType.newWordDict && store.newWordDict.wordList.length">
|
||||
<div class="my-button" @click="store.changeDict(store.newWordDict)">
|
||||
切换
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</swiper-slide>
|
||||
<swiper-slide>
|
||||
<WordList :active="store.sideIsOpen && tabIndex === 2" class="page" :word-list="store.skipWords" :index="0"/>
|
||||
<div class="page0">
|
||||
<header>
|
||||
<div class="dict-name">总词数:{{ store.skipWordDict.wordList.length }}</div>
|
||||
</header>
|
||||
<WordList
|
||||
class="word-list"
|
||||
@change="(e:number) => store.changeDict(store.skipWordDict,0,e)"
|
||||
:isActive="store.sideIsOpen && tabIndex === 2"
|
||||
:list="store.skipWordDict.wordList"
|
||||
:activeIndex="store.skipWordDict.wordIndex"/>
|
||||
<footer v-if="store.currentDictType.name !== DictType.skipWordDict && store.skipWordDict.wordList.length">
|
||||
<div class="my-button" @click="store.changeDict(store.skipWordDict,0,0)">
|
||||
切换
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</swiper-slide>
|
||||
</swiper>
|
||||
</div>
|
||||
@@ -85,11 +105,6 @@ provide('stepIndex', computed(() => stepIndex))
|
||||
theme="outline" size="20" fill="#929596"
|
||||
:strokeWidth="2"/>
|
||||
</template>
|
||||
<style>
|
||||
.page {
|
||||
padding: 15rem;
|
||||
}
|
||||
</style>
|
||||
<style scoped lang="scss">
|
||||
@import "@/assets/css/colors";
|
||||
|
||||
@@ -102,7 +117,7 @@ provide('stepIndex', computed(() => stepIndex))
|
||||
.side {
|
||||
$width: 20vw;
|
||||
width: $width;
|
||||
background: $dark-bg2;
|
||||
background: $dark-second-bg;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -115,7 +130,7 @@ provide('stepIndex', computed(() => stepIndex))
|
||||
|
||||
$header-height: 40rem;
|
||||
|
||||
header {
|
||||
& > header {
|
||||
height: $header-height;
|
||||
position: relative;
|
||||
display: flex;
|
||||
@@ -155,10 +170,50 @@ provide('stepIndex', computed(() => stepIndex))
|
||||
.mySwiper {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.swiper-slide {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
footer {
|
||||
padding-right: $space;
|
||||
height: 50rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.page0 {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
header {
|
||||
padding: 0 $space;
|
||||
height: $header-height;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10rem;
|
||||
font-size: 18rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.word-list {
|
||||
flex: 1;
|
||||
padding-bottom: $space;
|
||||
}
|
||||
}
|
||||
|
||||
.page1 {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
header {
|
||||
padding: 0 $space;
|
||||
height: $header-height;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10rem;
|
||||
font-size: 18rem;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,11 @@ import {watch} from "vue"
|
||||
import {useBaseStore} from "@/stores/base.ts"
|
||||
|
||||
const store = useBaseStore()
|
||||
const emit = defineEmits(['change'])
|
||||
const props = defineProps<{
|
||||
wordList: Word[],
|
||||
index: number,
|
||||
active: boolean
|
||||
list: Word[],
|
||||
activeIndex: number,
|
||||
isActive: boolean
|
||||
}>()
|
||||
|
||||
const [playAudio] = usePlayWordAudio()
|
||||
@@ -16,86 +17,97 @@ const listRef: HTMLElement = $ref(null as any)
|
||||
|
||||
function scrollViewToCenter(index: number) {
|
||||
if (index === -1) return
|
||||
listRef.children[index]!.scrollIntoView({block: 'center', behavior: 'smooth'})
|
||||
listRef.children[index]?.scrollIntoView({block: 'center', behavior: 'smooth'})
|
||||
}
|
||||
|
||||
watch(() => props.index, (n: any) => {
|
||||
watch(() => props.activeIndex, (n: any) => {
|
||||
if (store.sideIsOpen) {
|
||||
scrollViewToCenter(n)
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => props.active, (n: boolean) => {
|
||||
watch(() => props.isActive, (n: boolean) => {
|
||||
setTimeout(() => {
|
||||
if (n) scrollViewToCenter(props.index)
|
||||
if (n) scrollViewToCenter(props.activeIndex)
|
||||
}, 300)
|
||||
})
|
||||
|
||||
watch(() => props.list, () => {
|
||||
listRef.scrollTo(0, 0)
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="words">
|
||||
<div class="list" ref="listRef">
|
||||
<template v-for="(item,i) in wordList">
|
||||
<div class="item" :class="index === i && 'active'">
|
||||
<div class="left">
|
||||
<div class="letter">{{ item.name }}</div>
|
||||
<div class="info">
|
||||
<div class="translate">{{ item.trans.join(';') }}</div>
|
||||
<div class="phonetic">{{ item.usphone }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="audio" @click="playAudio(item.name)">播放</div>
|
||||
<div class="audio" @click="playAudio(item.name)">删除</div>
|
||||
<div class="list" ref="listRef">
|
||||
<template v-for="(item,i) in list">
|
||||
<div class="item" @click="$emit('change',i)" :class="activeIndex === i && 'active'">
|
||||
<div class="left">
|
||||
<div class="letter">{{ item.name }}</div>
|
||||
<div class="info">
|
||||
<div class="translate">{{ item.trans.join(';') }}</div>
|
||||
<div class="phonetic">{{ item.usphone }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="audio" @click="playAudio(item.name)">播放</div>
|
||||
<div class="audio" @click="playAudio(item.name)">删除</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/assets/css/colors";
|
||||
|
||||
.words {
|
||||
.list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15rem;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0 $space;
|
||||
overflow: auto;
|
||||
box-sizing: border-box;
|
||||
|
||||
.list {
|
||||
.item {
|
||||
background: $dark-main-bg;
|
||||
border-radius: 6rem;
|
||||
padding: 10rem;
|
||||
//border: 1px solid gray;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15rem;
|
||||
justify-content: space-between;
|
||||
transition: all .3s;
|
||||
|
||||
.item {
|
||||
border-radius: 10rem;
|
||||
padding: 10rem;
|
||||
border: 1px solid gray;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
&.active {
|
||||
background: $second;
|
||||
}
|
||||
|
||||
.left {
|
||||
.letter {
|
||||
margin-bottom: 10rem;
|
||||
font-size: 24rem;
|
||||
line-height: 1;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
|
||||
display: flex;
|
||||
}
|
||||
&:hover {
|
||||
background: $item-hover;
|
||||
}
|
||||
|
||||
.info {
|
||||
display: flex;
|
||||
gap: 20rem
|
||||
}
|
||||
}
|
||||
|
||||
.right {
|
||||
.left {
|
||||
.letter {
|
||||
margin-bottom: 10rem;
|
||||
font-size: 24rem;
|
||||
line-height: 1;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: $dark-bg;
|
||||
.info {
|
||||
display: flex;
|
||||
gap: 20rem
|
||||
}
|
||||
}
|
||||
|
||||
.right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,99 +1,128 @@
|
||||
import {defineStore} from 'pinia'
|
||||
import {Config, Dict, SaveKey, State, Word} from "../types.ts"
|
||||
import {chunk} from "lodash";
|
||||
import NCE_2 from "../assets/dicts/NCE_2.json";
|
||||
import {Config, Dict, DictType, SaveKey, State, Word} from "../types.ts"
|
||||
import {chunk, cloneDeep} from "lodash";
|
||||
|
||||
export const useBaseStore = defineStore('base', {
|
||||
state: (): State => {
|
||||
return {
|
||||
newWordDict: {
|
||||
wordList: [],
|
||||
chapterList: [],
|
||||
chapterIndex: -1,
|
||||
wordIndex: -1,
|
||||
},
|
||||
skipWordDict: {
|
||||
wordList: [],
|
||||
chapterList: [],
|
||||
chapterIndex: -1,
|
||||
wordIndex: -1,
|
||||
},
|
||||
dict: {
|
||||
name: '新概念英语-2',
|
||||
description: '新概念英语第二册',
|
||||
category: '青少年英语',
|
||||
tags: ['新概念英语'],
|
||||
url: '/dicts/NCE_2.json',
|
||||
length: 858,
|
||||
language: 'en',
|
||||
languageCategory: 'en',
|
||||
wordList: [],
|
||||
chapterList: [],
|
||||
chapterIndex: 0,
|
||||
wordIndex: 0,
|
||||
},
|
||||
currentDictType: {
|
||||
name: 'inner',
|
||||
dictUrl: '/dicts/NCE_2.json'
|
||||
},
|
||||
sideIsOpen: false,
|
||||
dictModalIsOpen: true,
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
skipWordNames: (state: State) => {
|
||||
return state.skipWordDict.wordList.map(v => v.name)
|
||||
state: (): State => {
|
||||
return {
|
||||
newWordDict: {
|
||||
type: DictType.newWordDict,
|
||||
wordList: [],
|
||||
chapterList: [],
|
||||
chapterIndex: -1,
|
||||
wordIndex: -1,
|
||||
},
|
||||
skipWordDict: {
|
||||
type: DictType.skipWordDict,
|
||||
wordList: [],
|
||||
chapterList: [],
|
||||
chapterIndex: -1,
|
||||
wordIndex: -1,
|
||||
},
|
||||
dict: {
|
||||
type: DictType.inner,
|
||||
name: '新概念英语-2',
|
||||
description: '新概念英语第二册',
|
||||
category: '青少年英语',
|
||||
tags: ['新概念英语'],
|
||||
url: '/dicts/NCE_2.json',
|
||||
length: 858,
|
||||
language: 'en',
|
||||
languageCategory: 'en',
|
||||
wordList: [],
|
||||
chapterList: [],
|
||||
chapterIndex: 0,
|
||||
wordIndex: 0,
|
||||
},
|
||||
currentDictType: {
|
||||
name: DictType.inner,
|
||||
dictUrl: '/dicts/NCE_2.json'
|
||||
},
|
||||
sideIsOpen: false,
|
||||
dictModalIsOpen: false,
|
||||
}
|
||||
},
|
||||
currentDict(state: State): Dict {
|
||||
switch (state.currentDictType.name) {
|
||||
case "newWordDict":
|
||||
return state.newWordDict
|
||||
case "skipWordDict":
|
||||
return state.skipWordDict
|
||||
case 'inner':
|
||||
case 'custom':
|
||||
return state.dict
|
||||
}
|
||||
getters: {
|
||||
skipWordNames: (state: State) => {
|
||||
return state.skipWordDict.wordList.map(v => v.name)
|
||||
},
|
||||
currentDict(state: State): Dict {
|
||||
switch (state.currentDictType.name) {
|
||||
case DictType.newWordDict:
|
||||
return state.newWordDict
|
||||
case DictType.skipWordDict:
|
||||
return state.skipWordDict
|
||||
case DictType.inner:
|
||||
case DictType.custom:
|
||||
return state.dict
|
||||
}
|
||||
},
|
||||
chapter(): Word[] {
|
||||
return this.currentDict.chapterList[this.currentDict.chapterIndex] ?? []
|
||||
},
|
||||
word(): Word {
|
||||
return this.chapter[this.currentDict.wordIndex] ?? {
|
||||
trans: [],
|
||||
name: ''
|
||||
}
|
||||
},
|
||||
},
|
||||
chapter(): Word[] {
|
||||
return this.currentDict.chapterList[this.currentDict.wordIndex] ?? []
|
||||
},
|
||||
word(): Word {
|
||||
return this.chapter[this.currentDict.wordIndex] ?? {
|
||||
trans: [],
|
||||
name: ''
|
||||
}
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
setState(obj: any) {
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
this[key] = value
|
||||
}
|
||||
},
|
||||
async init() {
|
||||
let configStr = localStorage.getItem(SaveKey)
|
||||
if (configStr) {
|
||||
let obj: Config = JSON.parse(configStr)
|
||||
this.setState(obj)
|
||||
}
|
||||
if (this.currentDictType.name === 'inner') {
|
||||
let r = await fetch(`/public/${this.currentDictType.dictUrl}`)
|
||||
r.json().then(v => {
|
||||
this.dict.wordList = v
|
||||
this.dict.chapterList = chunk(this.dict.wordList, 15)
|
||||
})
|
||||
}
|
||||
if (this.currentDictType.name === 'custom') {
|
||||
let r = await fetch(`/public/${this.currentDictType.dictUrl}`)
|
||||
r.json().then(v => {
|
||||
this.dict.wordList = v
|
||||
this.dict.chapterList = chunk(this.dict.wordList, 15)
|
||||
})
|
||||
}
|
||||
},
|
||||
changeDict() {
|
||||
actions: {
|
||||
setState(obj: any) {
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
this[key] = value
|
||||
}
|
||||
console.log('this/', this)
|
||||
},
|
||||
async init() {
|
||||
let configStr = localStorage.getItem(SaveKey)
|
||||
if (configStr) {
|
||||
let obj: Config = JSON.parse(configStr)
|
||||
this.setState(obj)
|
||||
}
|
||||
if (this.currentDictType.name === DictType.inner) {
|
||||
let r = await fetch(`/public/${this.currentDictType.dictUrl}`)
|
||||
r.json().then(v => {
|
||||
this.dict.wordList = v
|
||||
this.dict.chapterList = chunk(this.dict.wordList, 15)
|
||||
})
|
||||
}
|
||||
if (this.currentDictType.name === DictType.custom) {
|
||||
let r = await fetch(`/public/${this.currentDictType.dictUrl}`)
|
||||
r.json().then(v => {
|
||||
this.dict.wordList = v
|
||||
this.dict.chapterList = chunk(this.dict.wordList, 15)
|
||||
})
|
||||
}
|
||||
},
|
||||
async changeDict(dict: Dict, chapterIndex: number = -1, wordIndex: number = -1) {
|
||||
if ([DictType.newWordDict, DictType.skipWordDict].includes(dict.type)) {
|
||||
this.currentDictType.name = dict.type
|
||||
this.currentDictType.dictUrl = ''
|
||||
this[dict.type].chapterList = [this[dict.type].wordList]
|
||||
this[dict.type].chapterIndex = chapterIndex === -1 ? 0 : chapterIndex
|
||||
this[dict.type].wordIndex = wordIndex === -1 ? 0 : wordIndex
|
||||
} else {
|
||||
if (dict.name === this.dict.name) {
|
||||
this.currentDictType.name === dict.type
|
||||
this.currentDictType.dictUrl = dict.url
|
||||
if (wordIndex !== -1) this.dict.wordIndex = wordIndex
|
||||
if (chapterIndex !== -1) this.dict.chapterIndex = chapterIndex
|
||||
} else {
|
||||
// let r = await fetch(`/public/${dict.url}`)
|
||||
// r.json().then(v => {
|
||||
// this.currentDictType.name === dict.type
|
||||
// this.currentDictType.dictUrl = dict.url
|
||||
//
|
||||
// })
|
||||
this.dict = cloneDeep(dict)
|
||||
this.dict.chapterList = chunk(this.dict.wordList, 15)
|
||||
this.dict.chapterIndex = chapterIndex === -1 ? 0 : chapterIndex
|
||||
this.dict.wordIndex = wordIndex === -1 ? 0 : wordIndex
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
})
|
||||
160
src/types.ts
160
src/types.ts
@@ -1,16 +1,16 @@
|
||||
export type Config = {
|
||||
newWords: Word[],
|
||||
skipWords: Word[],
|
||||
skipWordNames: string[],
|
||||
dict: string,
|
||||
currentDict: {
|
||||
wordList: Word[],
|
||||
chapterList: Word[][],
|
||||
name: string,
|
||||
desc: string
|
||||
}
|
||||
chapterIndex: number,
|
||||
wordIndex: number,
|
||||
newWords: Word[],
|
||||
skipWords: Word[],
|
||||
skipWordNames: string[],
|
||||
dict: string,
|
||||
currentDict: {
|
||||
wordList: Word[],
|
||||
chapterList: Word[][],
|
||||
name: string,
|
||||
desc: string
|
||||
}
|
||||
chapterIndex: number,
|
||||
wordIndex: number,
|
||||
}
|
||||
export type Word = {"name": string, "usphone": string, "ukphone": string, "trans": string[]}
|
||||
|
||||
@@ -25,92 +25,102 @@ export type LanguageCategoryType = 'en' | 'ja' | 'de' | 'code'
|
||||
|
||||
|
||||
export type DictionaryResource = {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
category: string
|
||||
tags: string[]
|
||||
url: string
|
||||
length: number
|
||||
language: LanguageType
|
||||
languageCategory: LanguageCategoryType
|
||||
//override default pronunciation when not undefined
|
||||
defaultPronIndex?: number
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
category: string
|
||||
tags: string[]
|
||||
url: string
|
||||
length: number
|
||||
language: LanguageType
|
||||
languageCategory: LanguageCategoryType
|
||||
//override default pronunciation when not undefined
|
||||
defaultPronIndex?: number
|
||||
}
|
||||
|
||||
export type Dictionary = {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
category: string
|
||||
tags: string[]
|
||||
url: string
|
||||
length: number
|
||||
language: LanguageType
|
||||
languageCategory: LanguageCategoryType
|
||||
// calculated in the store
|
||||
chapterCount: number
|
||||
//override default pronunciation when not undefined
|
||||
defaultPronIndex?: number
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
category: string
|
||||
tags: string[]
|
||||
url: string
|
||||
length: number
|
||||
language: LanguageType
|
||||
languageCategory: LanguageCategoryType
|
||||
// calculated in the store
|
||||
chapterCount: number
|
||||
//override default pronunciation when not undefined
|
||||
defaultPronIndex?: number
|
||||
}
|
||||
|
||||
export type PronunciationConfig = {
|
||||
name: string
|
||||
pron: PronunciationType
|
||||
name: string
|
||||
pron: PronunciationType
|
||||
}
|
||||
|
||||
export type LanguagePronunciationMapConfig = {
|
||||
defaultPronIndex: number
|
||||
pronunciation: PronunciationConfig[]
|
||||
defaultPronIndex: number
|
||||
pronunciation: PronunciationConfig[]
|
||||
}
|
||||
|
||||
export type LanguagePronunciationMap = {
|
||||
[key in LanguageType]: LanguagePronunciationMapConfig
|
||||
[key in LanguageType]: LanguagePronunciationMapConfig
|
||||
}
|
||||
|
||||
export type SoundResource = {
|
||||
key: string
|
||||
name: string
|
||||
filename: string
|
||||
key: string
|
||||
name: string
|
||||
filename: string
|
||||
}
|
||||
|
||||
|
||||
export interface DictJson {
|
||||
name: string,
|
||||
description: string,
|
||||
category: string,
|
||||
tags: string[],
|
||||
url: string,
|
||||
length: number,
|
||||
language: string,
|
||||
languageCategory: string,
|
||||
name: string,
|
||||
description: string,
|
||||
category: string,
|
||||
tags: string[],
|
||||
url: string,
|
||||
length: number,
|
||||
language: string,
|
||||
languageCategory: string,
|
||||
}
|
||||
|
||||
export enum DictType {
|
||||
newWordDict = 'newWordDict',
|
||||
skipWordDict = 'skipWordDict',
|
||||
inner = 'inner',
|
||||
custom = 'custom',
|
||||
}
|
||||
|
||||
export interface Dict extends DictJson {
|
||||
wordList: Word[],
|
||||
chapterList: Word[][],
|
||||
chapterIndex: number,
|
||||
wordIndex: number,
|
||||
type: DictType,
|
||||
wordList: Word[],
|
||||
chapterList: Word[][],
|
||||
chapterIndex: number,
|
||||
wordIndex: number,
|
||||
}
|
||||
|
||||
export interface State {
|
||||
newWordDict: {
|
||||
wordList: Word[],
|
||||
chapterList: Word[][],
|
||||
chapterIndex: number,
|
||||
wordIndex: number,
|
||||
},
|
||||
skipWordDict: {
|
||||
wordList: Word[],
|
||||
chapterList: Word[][],
|
||||
chapterIndex: number,
|
||||
wordIndex: number,
|
||||
},
|
||||
dict: Dict,
|
||||
currentDictType: {
|
||||
name: 'newWordDict' | 'skipWordDict' | 'inner' | 'custom',
|
||||
dictUrl: string
|
||||
}
|
||||
sideIsOpen: boolean,
|
||||
dictModalIsOpen: boolean,
|
||||
newWordDict: {
|
||||
type: DictType,
|
||||
wordList: Word[],
|
||||
chapterList: Word[][],
|
||||
chapterIndex: number,
|
||||
wordIndex: number,
|
||||
},
|
||||
skipWordDict: {
|
||||
type: DictType,
|
||||
wordList: Word[],
|
||||
chapterList: Word[][],
|
||||
chapterIndex: number,
|
||||
wordIndex: number,
|
||||
},
|
||||
dict: Dict,
|
||||
currentDictType: {
|
||||
name: DictType,
|
||||
dictUrl: string
|
||||
}
|
||||
sideIsOpen: boolean,
|
||||
dictModalIsOpen: boolean,
|
||||
}
|
||||
Reference in New Issue
Block a user