This commit is contained in:
zyronon
2023-08-07 18:53:42 +08:00
parent d6561ad331
commit 54dad1ea83
13 changed files with 2919 additions and 286 deletions

View File

@@ -140,9 +140,6 @@ async function onKeyDown(e: KeyboardEvent) {
const [playAudio] = usePlayWordAudio()
const openSide = $ref(true)
provide('sideIsOpen', computed(() => openSide))
</script>
<template>
@@ -171,7 +168,7 @@ provide('sideIsOpen', computed(() => openSide))
</div>
</div>
</div>
<Side v-model="openSide"/>
<Side/>
</div>
</template>

1873
src/assets/dictionary.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,16 @@
<script setup lang="ts">
import {ArrowLeft} from '@icon-park/vue-next'
import {inject} from "vue"
import {computed, inject} from "vue"
import WordList from "@/components/WordList.vue"
import {useBaseStore} from "@/stores/base.ts"
const store = useBaseStore()
const back = inject('back')
const stepIndex = inject('stepIndex')
const tabIndex = inject('tabIndex')
const isActive = computed(() => {
return stepIndex.value === 2 && tabIndex.value === 0 && store.sideIsOpen
})
</script>
@@ -15,7 +20,7 @@ const back = inject('back')
<arrow-left @click="back" theme="outline" size="20" fill="#929596" :strokeWidth="2"/>
<div class="dict-name">16.</div>
</header>
<WordList :word-list="store.chapter" :index="store.wordIndex"></WordList>
<WordList :active="isActive" :word-list="store.chapter" :index="store.wordIndex"></WordList>
</div>
</template>

View File

@@ -6,25 +6,23 @@ import {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, ref} from "vue"
import {computed, onMounted, provide} from "vue"
import ChapterDetail from "@/components/ChapterDetail.vue"
import {Swiper, SwiperSlide} from 'swiper/vue';
import 'swiper/css';
import {Swiper as SwiperClass} from "swiper/types"
const store = useBaseStore()
const props = defineProps({
modelValue: Boolean,
})
defineEmits(['update:modelValue'])
const swiperIns0: SwiperClass = $ref(null)
const swiperIns1: SwiperClass = $ref(null)
const swiperIns0: SwiperClass = $ref(null as any)
const swiperIns1: SwiperClass = $ref(null as any)
onMounted(() => {
})
let tabIndex = $ref(0)
let stepIndex = $ref(0)
function slideTo(index: number) {
swiperIns0.slideTo(index)
@@ -42,6 +40,8 @@ function back() {
provide('next', next)
provide('back', back)
provide('tabIndex', computed(() => tabIndex))
provide('stepIndex', computed(() => stepIndex))
</script>
<template>
@@ -59,7 +59,9 @@ provide('back', back)
<div class="side-content">
<swiper @swiper="e=>swiperIns0 = e" class="mySwiper" :allow-touch-move="false">
<swiper-slide>
<swiper @swiper="e=>swiperIns1 = e" class="mySwiper" :allow-touch-move="false">
<swiper @swiper="e=>swiperIns1 = e"
@activeIndexChange="e=>stepIndex = e.activeIndex"
class="mySwiper" :allow-touch-move="false">
<swiper-slide>
<DictList/>
</swiper-slide>
@@ -72,15 +74,15 @@ provide('back', back)
</swiper>
</swiper-slide>
<swiper-slide>
<WordList class="page" :word-list="store.newWords" :index="0"/>
<WordList :active="store.sideIsOpen && tabIndex === 1" class="page" :word-list="store.newWords" :index="0"/>
</swiper-slide>
<swiper-slide>
<WordList class="page" :word-list="store.skipWords" :index="0"/>
<WordList :active="store.sideIsOpen && tabIndex === 2" class="page" :word-list="store.skipWords" :index="0"/>
</swiper-slide>
</swiper>
</div>
</div>
<menu-fold v-if="!modelValue" class="menu" @click="$emit('update:modelValue', true)"
<menu-fold v-if="!store.sideIsOpen" class="menu" @click="store.sideIsOpen = true"
theme="outline" size="20" fill="#929596"
:strokeWidth="2"/>
</template>

View File

@@ -1,204 +0,0 @@
<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 {$ref} from "vue/macros"
import DictList from "@/components/DictList.vue"
import ChapterList from "@/components/ChapterList.vue"
import {provide} from "vue"
import ChapterDetail from "@/components/ChapterDetail.vue"
import {Swiper, SwiperSlide} from 'swiper/vue';
import 'swiper/css';
const store = useBaseStore()
const props = defineProps({
modelValue: Boolean,
})
defineEmits(['update:modelValue'])
let step = $ref(0)
let tabIndex = $ref(0)
function next() {
step++
}
function back() {
step--
}
provide('next', next)
provide('back', back)
const onSwiper = (swiper) => {
console.log(swiper);
};
const onSlideChange = () => {
console.log('slide change');
};
</script>
<template>
<div class="side" :class="modelValue&&'open'">
<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">生词本</div>
<div class="tab" :class="tabIndex===2&&'active'" @click="tabIndex = 2">已忽略</div>
</div>
<arrow-right class="close"
@click="$emit('update:modelValue', false)"
theme="outline" size="20" fill="#929596" :strokeWidth="2"/>
</header>
<div class="side-content">
<swiper
:slides-per-view="3"
:space-between="50"
@swiper="onSwiper"
@slideChange="onSlideChange"
>
<swiper-slide>
<DictList/>
</swiper-slide>
<swiper-slide>
<WordList class="page" :word-list="store.newWords" :index="0"/>
</swiper-slide>
<swiper-slide>
<WordList class="page" :word-list="store.skipWords" :index="0"/>
</swiper-slide>
</swiper>
</div>
<!-- <div class="wrapper">-->
<!-- <div class="pages" v-if="tabIndex === 0" :class="`step${step}`">-->
<!-- <DictList/>-->
<!-- <ChapterList/>-->
<!-- <ChapterDetail/>-->
<!-- </div>-->
<!-- <WordList class="page" v-if="tabIndex === 1" :word-list="store.newWords" :index="0"></WordList>-->
<!-- <WordList v-if="tabIndex === 2" :word-list="store.skipWords" :index="0"></WordList>-->
<!-- </div>-->
</div>
<menu-fold v-if="!modelValue" class="menu" @click="$emit('update:modelValue', true)"
theme="outline" size="20" fill="#929596"
:strokeWidth="2"/>
</template>
<style scoped lang="scss">
@import "@/assets/css/colors";
.side {
$width: 20vw;
background: $dark-bg2;
width: $width;
height: 100%;
display: flex;
flex-direction: column;
transition: all .3s;
margin-right: -$width;
&.open {
margin-right: 0;
}
header {
position: relative;
display: flex;
align-items: center;
.tabs {
padding: 10rem 20rem;
width: 100%;
display: flex;
align-items: flex-end;
border-bottom: 1px solid #e1e1e1;
gap: 15rem;
font-size: 14rem;
color: gray;
.tab {
cursor: pointer;
&.active {
font-size: 16rem;
color: rgb(36, 127, 255);
font-weight: bold;
}
}
}
.close {
cursor: pointer;
position: absolute;
right: 20rem;
}
}
.side-content {
//flex: 1;
.swiper {
width: 100%;
height: 100%;
}
}
.wrapper {
flex: 1;
overflow: hidden;
.pages {
width: 20vw * 3;
height: 100%;
display: flex;
transition: all .3s;
&.step0 {
transform: translate3d(0, 0, 0);
}
&.step1 {
transform: translate3d(-20vw, 0, 0);
}
&.step2 {
transform: translate3d(-40vw, 0, 0);
}
}
}
}
.menu {
position: fixed;
right: 20rem;
top: 20rem;
}
.swiper {
width: 100%;
height: 100%;
}
.swiper-slide {
text-align: center;
font-size: 18px;
/* Center slide text vertically */
display: flex;
justify-content: center;
align-items: center;
}
.swiper-slide img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
</style>

View File

@@ -1,19 +1,29 @@
<script setup lang="ts">
import {Word} from "../types";
import {usePlayWordAudio} from "../hooks/usePlayWordAudio";
import {inject, nextTick, watch} from "vue"
import {watch} from "vue"
import {useBaseStore} from "@/stores/base.ts"
const sideIsOpen = inject('sideIsOpen')
const props = defineProps<{wordList: Word[], index: number}>()
const store = useBaseStore()
const props = defineProps<{wordList: Word[], index: number, active: boolean}>()
const [playAudio] = usePlayWordAudio()
const listRef: HTMLElement = $ref(null)
watch(() => props.index, (n: number) => {
if (sideIsOpen.value) {
nextTick(() => {
listRef.querySelector('.active').scrollIntoView({block: 'center', behavior: 'smooth'})
})
const listRef: HTMLElement = $ref(null as any)
function scrollViewToCenter(index: number) {
listRef.children[index]!.scrollIntoView({block: 'center', behavior: 'smooth'})
}
watch(() => props.index, (n: any) => {
if (store.sideIsOpen) {
scrollViewToCenter(n)
}
})
watch(() => props.active, (n: boolean) => {
setTimeout(() => {
if (n) scrollViewToCenter(props.index)
}, 300)
})
</script>
<template>

View File

@@ -27,6 +27,7 @@ export function useSound(srcList?: string[], num?: number) {
audioList[index % audioLength].play()
}
}
return [
play,
setAudio

View File

@@ -4,57 +4,62 @@ import {chunk} from "lodash";
import NCE_2 from "../assets/dicts/NCE_2.json";
export const useBaseStore = defineStore('base', {
state: () => {
return {
newWords: [],
skipWords: [],
skipWordNames: [],
wordList: [],
wordListSplit: [],
dict: 'nce2',
chapterIndex: 0,
wordIndex: 0,
sideIsOpen: false,
}
state: () => {
return {
newWords: [],
skipWords: [],
skipWordNames: [],
currentDict: {
wordList: [],
chapterList: [],
name: '新概念第二册',
desc: '',
},
chapterIndex: 0,
wordIndex: 0,
sideIsOpen: false,
}
},
getters: {
chapter: (state): Word[] => {
return state.currentDict.chapterList?.[state.chapterIndex] ?? []
},
getters: {
chapter: (state): Word[] => {
return state.wordListSplit?.[state.chapterIndex] ?? []
},
word(state): Word {
return this.chapter[state.wordIndex] ?? {
trans: [],
name: ''
}
},
word(state): Word {
return this.chapter[state.wordIndex] ?? {
trans: [],
name: ''
}
},
actions: {
init() {
let configStr = localStorage.getItem(SaveKey)
if (configStr) {
let obj: Config = JSON.parse(configStr)
this.newWords = obj.newWords
this.skipWords = obj.skipWords
this.skipWordNames = obj.skipWordNames
this.dict = obj.dict
this.chapterIndex = obj.chapterIndex
this.wordIndex = 0
}
},
actions: {
setState(obj: any) {
for (const [key, value] of Object.entries(obj)) {
this[key] = value
}
},
init() {
let configStr = localStorage.getItem(SaveKey)
if (configStr) {
let obj: Config = JSON.parse(configStr)
this.newWords = obj.newWords
this.skipWords = obj.skipWords
this.skipWordNames = obj.skipWordNames
this.currentDict = obj.currentDict
this.chapterIndex = obj.chapterIndex
this.wordIndex = 0
}
if (this.currentDict.name === '新概念第二册') {
this.currentDict.wordList = NCE_2
this.currentDict.chapterList = chunk(NCE_2, 15)
// console.log('this.wordListSplit', this.wordListSplit)
// let wordTemp = wordList?.[config.chapterIndex]?.[config.wordIndex]
// if (wordTemp && config.skipWordNames.includes(wordTemp.name)) {
// next()
// }
}
},
changeDict() {
if (this.dict === 'nce2') {
this.wordList = NCE_2
this.wordListSplit = chunk(this.wordList, 15)
// console.log('this.wordListSplit', this.wordListSplit)
// let wordTemp = wordList?.[config.chapterIndex]?.[config.wordIndex]
// if (wordTemp && config.skipWordNames.includes(wordTemp.name)) {
// next()
// }
}
},
setState(obj: any) {
for (const [key, value] of Object.entries(obj)) {
this[key] = value
}
}
},
}
},
})

View File

@@ -3,11 +3,73 @@ export type Config = {
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[] }
export type Word = {"name": string, "usphone": string, "ukphone": string, "trans": string[]}
export const SaveKey = 'bb-word-config'
export const PronunciationApi = 'https://dict.youdao.com/dictvoice?audio='
export type PronunciationType = 'us' | 'uk' | 'romaji' | 'zh' | 'ja' | 'de'
export type PhoneticType = 'us' | 'uk' | 'romaji' | 'zh' | 'ja' | 'de'
export type LanguageType = 'en' | 'romaji' | 'zh' | 'ja' | 'code' | 'de'
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
}
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
}
export type PronunciationConfig = {
name: string
pron: PronunciationType
}
export type LanguagePronunciationMapConfig = {
defaultPronIndex: number
pronunciation: PronunciationConfig[]
}
export type LanguagePronunciationMap = {
[key in LanguageType]: LanguagePronunciationMapConfig
}
export type SoundResource = {
key: string
name: string
filename: string
}

12
src/vite-env.d.ts vendored
View File

@@ -4,9 +4,21 @@
// const src: string
// export default src
// }
declare module '*.mp3' {
const src: string;
export default src;
}
declare module "*.vue" {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
// declare module '*.vue' {
// import Vue from 'vue'
// export default Vue
// }
declare module '*.ts';