添加侧边栏
This commit is contained in:
194
src/App.vue
194
src/App.vue
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import {onMounted, onUnmounted, watch} from "vue"
|
||||
import {computed, onMounted, onUnmounted, provide, watch} from "vue"
|
||||
import 快速打字的机械键盘声音Mp3 from './assets/sound/key-sounds/快速打字的机械键盘声音.mp3'
|
||||
import 键盘快速打字的声音Mp3 from './assets/sound/key-sounds/键盘快速打字的声音.mp3'
|
||||
import 电话打字的声音Mp3 from './assets/sound/key-sounds/电话打字的声音.mp3'
|
||||
@@ -15,6 +15,8 @@ import {useSound} from "@/hooks/useSound.ts"
|
||||
import {useBaseStore} from "@/stores/base.ts"
|
||||
import {SaveKey} from "./types";
|
||||
import WordList from "./components/WordList.vue";
|
||||
import Side from "@/components/Side.vue"
|
||||
import {usePlayWordAudio} from "@/hooks/usePlayWordAudio.ts"
|
||||
|
||||
let input = $ref('')
|
||||
let wrong = $ref('')
|
||||
@@ -23,9 +25,9 @@ let isDictation = $ref(true)
|
||||
let activeIndex = $ref(-1)
|
||||
const store = useBaseStore()
|
||||
|
||||
// const [play, setAudio] = useSound([机械0, 机械1, 机械2, 机械3], 1)
|
||||
const [playKeySound, setAudio] = useSound([老式机械], 3)
|
||||
// const [play, setAudio] = useSound([电话打字的声音Mp3], 3)
|
||||
const [playKeySound, setAudio] = useSound([机械0, 机械1, 机械2, 机械3], 1)
|
||||
// const [playKeySound, setAudio] = useSound([老式机械], 3)
|
||||
// const [playKeySound, setAudio] = useSound([电话打字的声音Mp3], 3)
|
||||
const [playBeep] = useSound([beep], 1)
|
||||
const [playCorrect] = useSound([correct], 1)
|
||||
const keyMap = {
|
||||
@@ -95,7 +97,7 @@ async function onKeyDown(e: KeyboardEvent) {
|
||||
}
|
||||
if (input === store.word.name) {
|
||||
playCorrect()
|
||||
setTimeout(next, 300)
|
||||
setTimeout(next, 200)
|
||||
}
|
||||
} else {
|
||||
// console.log('e', e)
|
||||
@@ -136,23 +138,11 @@ async function onKeyDown(e: KeyboardEvent) {
|
||||
}
|
||||
}
|
||||
|
||||
const pronunciationApi = 'https://dict.youdao.com/dictvoice?audio='
|
||||
const [playAudio] = usePlayWordAudio()
|
||||
|
||||
function generateWordSoundSrc(word: string, pronunciation: string) {
|
||||
switch (pronunciation) {
|
||||
case 'uk':
|
||||
return `${pronunciationApi}${word}&type=1`
|
||||
case 'us':
|
||||
return `${pronunciationApi}${word}&type=2`
|
||||
}
|
||||
}
|
||||
const openSide = $ref(true)
|
||||
provide('sideIsOpen', computed(() => openSide))
|
||||
|
||||
function playAudio(word) {
|
||||
let audio = new Audio(generateWordSoundSrc(word, 'us'))
|
||||
audio.play()
|
||||
}
|
||||
|
||||
const step = $ref(1)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -181,52 +171,18 @@ const step = $ref(1)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="side">
|
||||
<header>
|
||||
<div class="tabs">
|
||||
<div class="tab">单词表</div>
|
||||
<div class="tab active">生词本</div>
|
||||
<div class="tab">已忽略</div>
|
||||
</div>
|
||||
<div class="close">关闭</div>
|
||||
</header>
|
||||
<div class="wrapper">
|
||||
<WordList :word-list="store.newWords" :index="0"></WordList>
|
||||
|
||||
<div class="pages" v-if="false" :class="`step${step}`">
|
||||
<div class="dict page">
|
||||
<div class="tags">
|
||||
<div class="tag" :class="i === 5 &&'active'" v-for="i in 2">六级</div>
|
||||
</div>
|
||||
<div class="dict-list">
|
||||
<div class="dict-item" v-for="i in 5" @click="step = 1">
|
||||
<div class="name">CET-4</div>
|
||||
<div class="desc">大学英语四级词库</div>
|
||||
<div class="num">2607词</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chapter page">
|
||||
<div class="dict-name">CET-4</div>
|
||||
<div class="chapter-list">
|
||||
<div class="chapter-item" v-for="i in 10">
|
||||
<div class="title">1.A private conversation</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Side v-model="openSide"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/assets/css/colors";
|
||||
|
||||
.main-page {
|
||||
background: rgb(17, 24, 39);
|
||||
width: 100%;
|
||||
background: $dark-bg;
|
||||
width: 100vw;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
font-size: 14rem;
|
||||
|
||||
@@ -300,126 +256,6 @@ const step = $ref(1)
|
||||
}
|
||||
}
|
||||
|
||||
.side {
|
||||
width: 20vw;
|
||||
height: 100%;
|
||||
background: white;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
header {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.tabs {
|
||||
padding: 10rem 20rem;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
border-bottom: 1px solid #e1e1e1;
|
||||
gap: 15rem;
|
||||
font-size: 18rem;
|
||||
color: gray;
|
||||
|
||||
.tab {
|
||||
cursor: pointer;
|
||||
|
||||
&.active {
|
||||
font-size: 22rem;
|
||||
color: blue;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.close {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 20rem;
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
|
||||
.pages {
|
||||
width: 20vw * 2;
|
||||
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);
|
||||
}
|
||||
|
||||
.page {
|
||||
width: 20vw;
|
||||
padding: 10rem;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dict {
|
||||
.tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 20rem;
|
||||
|
||||
.tag {
|
||||
cursor: pointer;
|
||||
padding: 5rem 10rem;
|
||||
border-radius: 20rem;
|
||||
|
||||
&.active {
|
||||
background: blue;
|
||||
color: whitesmoke;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dict-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 10rem;
|
||||
|
||||
.dict-item {
|
||||
cursor: pointer;
|
||||
padding: 10rem;
|
||||
border-radius: 10rem;
|
||||
border: 1px solid gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chapter {
|
||||
.dict-name {
|
||||
font-size: 26rem;
|
||||
margin-bottom: 10rem;
|
||||
}
|
||||
|
||||
.chapter-list {
|
||||
.chapter-item {
|
||||
cursor: pointer;
|
||||
margin-bottom: 10rem;
|
||||
padding: 10rem;
|
||||
border-radius: 10rem;
|
||||
border: 1px solid gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@keyframes shake {
|
||||
|
||||
2
src/assets/css/colors.scss
Normal file
2
src/assets/css/colors.scss
Normal file
@@ -0,0 +1,2 @@
|
||||
$dark-bg: rgb(46, 46, 46);
|
||||
$dark-bg2: rgb(72, 72, 72);
|
||||
13
src/assets/css/style.scss
Normal file
13
src/assets/css/style.scss
Normal file
@@ -0,0 +1,13 @@
|
||||
@import "@icon-park/vue-next/styles/index.css";
|
||||
|
||||
html, body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#app {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
35
src/components/ChapterDetail.vue
Normal file
35
src/components/ChapterDetail.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<script setup lang="ts">
|
||||
import {ArrowLeft} from '@icon-park/vue-next'
|
||||
import {inject} from "vue"
|
||||
import WordList from "@/components/WordList.vue"
|
||||
import {useBaseStore} from "@/stores/base.ts"
|
||||
|
||||
const store = useBaseStore()
|
||||
const back = inject('back')
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="chapter-detail page">
|
||||
<header>
|
||||
<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>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.chapter-wrapper {
|
||||
header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10rem;
|
||||
margin-bottom: 10rem;
|
||||
|
||||
.dict-name {
|
||||
font-size: 26rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
48
src/components/ChapterList.vue
Normal file
48
src/components/ChapterList.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<script setup lang="ts">
|
||||
import {ArrowLeft} from '@icon-park/vue-next'
|
||||
import {inject} from "vue"
|
||||
|
||||
const back = inject('back')
|
||||
const next = inject('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>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.chapter-wrapper {
|
||||
header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10rem;
|
||||
margin-bottom: 10rem;
|
||||
|
||||
.dict-name {
|
||||
font-size: 26rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.chapter-list {
|
||||
.chapter-item {
|
||||
cursor: pointer;
|
||||
margin-bottom: 10rem;
|
||||
padding: 10rem;
|
||||
border-radius: 10rem;
|
||||
border: 1px solid gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
56
src/components/DictList.vue
Normal file
56
src/components/DictList.vue
Normal file
@@ -0,0 +1,56 @@
|
||||
<script setup lang="ts">
|
||||
import {inject} from "vue"
|
||||
|
||||
const next = inject('next')
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dict-wrapper page">
|
||||
<div class="tags">
|
||||
<div class="tag" :class="i === 5 &&'active'" v-for="i in 2">六级</div>
|
||||
</div>
|
||||
<div class="dict-list">
|
||||
<div class="dict-item" v-for="i in 5" @click="next">
|
||||
<div class="name">CET-4</div>
|
||||
<div class="desc">大学英语四级词库</div>
|
||||
<div class="num">2607词</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.dict-wrapper {
|
||||
.tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 20rem;
|
||||
|
||||
.tag {
|
||||
cursor: pointer;
|
||||
padding: 5rem 10rem;
|
||||
border-radius: 20rem;
|
||||
|
||||
&.active {
|
||||
background: gray;
|
||||
color: whitesmoke;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dict-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 10rem;
|
||||
|
||||
.dict-item {
|
||||
cursor: pointer;
|
||||
padding: 10rem;
|
||||
border-radius: 10rem;
|
||||
border: 1px solid gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
165
src/components/Side.vue
Normal file
165
src/components/Side.vue
Normal file
@@ -0,0 +1,165 @@
|
||||
<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 {computed, onMounted, provide, ref} 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)
|
||||
|
||||
onMounted(() => {
|
||||
})
|
||||
|
||||
let tabIndex = $ref(0)
|
||||
|
||||
function slideTo(index: number) {
|
||||
swiperIns0.slideTo(index)
|
||||
tabIndex = index
|
||||
}
|
||||
|
||||
|
||||
function next() {
|
||||
swiperIns1.slideNext()
|
||||
}
|
||||
|
||||
function back() {
|
||||
swiperIns1.slidePrev()
|
||||
}
|
||||
|
||||
provide('next', next)
|
||||
provide('back', back)
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div class="side" :class="store.sideIsOpen && 'open'">
|
||||
<header>
|
||||
<div class="tabs">
|
||||
<div class="tab" :class="tabIndex===0&&'active'" @click="slideTo(0)">单词表</div>
|
||||
<div class="tab" :class="tabIndex===1&&'active'" @click="slideTo(1)">生词本</div>
|
||||
<div class="tab" :class="tabIndex===2&&'active'" @click="slideTo(2)">已忽略</div>
|
||||
</div>
|
||||
<arrow-right class="close"
|
||||
@click="store.sideIsOpen = false"
|
||||
theme="outline" size="20" fill="#929596" :strokeWidth="2"/>
|
||||
</header>
|
||||
<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-slide>
|
||||
<DictList/>
|
||||
</swiper-slide>
|
||||
<swiper-slide>
|
||||
<ChapterList/>
|
||||
</swiper-slide>
|
||||
<swiper-slide>
|
||||
<ChapterDetail/>
|
||||
</swiper-slide>
|
||||
</swiper>
|
||||
</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>
|
||||
<menu-fold v-if="!modelValue" class="menu" @click="$emit('update:modelValue', true)"
|
||||
theme="outline" size="20" fill="#929596"
|
||||
:strokeWidth="2"/>
|
||||
</template>
|
||||
<style>
|
||||
.page {
|
||||
padding: 15rem;
|
||||
}
|
||||
</style>
|
||||
<style scoped lang="scss">
|
||||
@import "@/assets/css/colors";
|
||||
|
||||
.menu {
|
||||
position: fixed;
|
||||
right: 20rem;
|
||||
top: 20rem;
|
||||
}
|
||||
|
||||
.side {
|
||||
$width: 20vw;
|
||||
width: $width;
|
||||
background: $dark-bg2;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transition: all .3s;
|
||||
margin-right: -$width;
|
||||
|
||||
&.open {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
$header-height: 40rem;
|
||||
|
||||
header {
|
||||
height: $header-height;
|
||||
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 {
|
||||
height: calc(100% - $header-height);
|
||||
|
||||
.mySwiper {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.swiper-slide {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
204
src/components/Side2.vue
Normal file
204
src/components/Side2.vue
Normal file
@@ -0,0 +1,204 @@
|
||||
<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>
|
||||
@@ -1,45 +1,57 @@
|
||||
<script setup lang="ts">
|
||||
import {Word} from "../types";
|
||||
import {usePlayWordAudio} from "../hooks/usePlayWordAudio";
|
||||
import {inject, nextTick, watch} from "vue"
|
||||
|
||||
const props = defineProps<{ wordList: Word[], index: number }>()
|
||||
const sideIsOpen = inject('sideIsOpen')
|
||||
const props = defineProps<{wordList: Word[], index: number}>()
|
||||
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'})
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="words">
|
||||
<div class="list">
|
||||
<div class="item" v-for="item in props.wordList">
|
||||
<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 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>
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="audio" @click="playAudio(item.name)">播放</div>
|
||||
<div class="audio" @click="playAudio(item.name)">删除</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/assets/css/colors";
|
||||
|
||||
.words {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
|
||||
.list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15rem;
|
||||
|
||||
.item {
|
||||
margin: 10rem;
|
||||
border-radius: 10rem;
|
||||
padding: 10rem;
|
||||
border: 1px solid blue;
|
||||
border: 1px solid gray;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
@@ -63,6 +75,10 @@ const [playAudio] = usePlayWordAudio()
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: $dark-bg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {createApp} from 'vue'
|
||||
import './style.css'
|
||||
import './assets/css/style.scss'
|
||||
import App from './App.vue'
|
||||
import {createPinia} from "pinia"
|
||||
|
||||
|
||||
@@ -4,56 +4,57 @@ 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,
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
chapter: (state): Word[] => {
|
||||
return state.wordListSplit?.[state.chapterIndex] ?? []
|
||||
state: () => {
|
||||
return {
|
||||
newWords: [],
|
||||
skipWords: [],
|
||||
skipWordNames: [],
|
||||
wordList: [],
|
||||
wordListSplit: [],
|
||||
dict: 'nce2',
|
||||
chapterIndex: 0,
|
||||
wordIndex: 0,
|
||||
sideIsOpen: false,
|
||||
}
|
||||
},
|
||||
word(state): Word {
|
||||
return this.chapter[state.wordIndex] ?? {
|
||||
trans: [],
|
||||
name: ''
|
||||
}
|
||||
getters: {
|
||||
chapter: (state): Word[] => {
|
||||
return state.wordListSplit?.[state.chapterIndex] ?? []
|
||||
},
|
||||
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: {
|
||||
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
|
||||
}
|
||||
|
||||
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()
|
||||
// }
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
},
|
||||
setState(obj: any) {
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
this[key] = value
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -1,11 +0,0 @@
|
||||
html, body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#app {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
Reference in New Issue
Block a user