This commit is contained in:
zyronon
2023-10-25 01:01:00 +08:00
parent a3e002b116
commit 056d1044c2
8 changed files with 322 additions and 317 deletions

View File

@@ -53,3 +53,4 @@ http://enpuz.com/ 语法分析工具
背单词页面div位置应该恒定不应该随翻译内容变动而跳动
点击句子播放的音乐,需要可暂停

6
components.d.ts vendored
View File

@@ -40,12 +40,10 @@ declare module 'vue' {
Fireworks: typeof import('./src/components/Fireworks.vue')['default']
Footer: typeof import('./src/components/Practice/Footer.vue')['default']
IconWrapper: typeof import('./src/components/IconWrapper.vue')['default']
Index: typeof import('./src/components/Practice/PracticeArticle/Index.vue')['default']
Input: typeof import('./src/components/Input.vue')['default']
List: typeof import('./src/components/List.vue')['default']
MiniModal: typeof import('./src/components/MiniModal.vue')['default']
Modal: typeof import('./src/components/Modal/Modal.vue')['default']
Panel: typeof import('./src/components/Practice/TypingArticle/Panel.vue')['default']
PopConfirm: typeof import('./src/components/PopConfirm.vue')['default']
Practice: typeof import('./src/components/Practice/Practice.vue')['default']
PracticeArticle: typeof import('./src/components/Practice/PracticeArticle/PracticeArticle.vue')['default']
@@ -57,10 +55,6 @@ declare module 'vue' {
Toolbar: typeof import('./src/components/Toolbar/Toolbar.vue')['default']
Tooltip: typeof import('./src/components/Tooltip.vue')['default']
TranslateSetting: typeof import('./src/components/Toolbar/TranslateSetting.vue')['default']
TypeArticle: typeof import('./src/components/Practice/TypingArticle/TypeArticle.vue')['default']
TypeWord: typeof import('./src/components/Practice/PracticeWord/TypeWord.vue')['default']
Typing: typeof import('./src/components/Practice/TypingArticle/Typing.vue')['default']
TypingArti: typeof import('./src/components/Practice/TypingArticle/TypingArti.vue')['default']
TypingArticle: typeof import('./src/components/Practice/PracticeArticle/TypingArticle.vue')['default']
TypingWord: typeof import('./src/components/Practice/PracticeWord/TypingWord.vue')['default']
VolumeIcon: typeof import('./src/components/VolumeIcon.vue')['default']

View File

@@ -86,7 +86,7 @@ onUnmounted(() => {
margin-bottom: 10rem;
transition: all .3s;
position: relative;
margin-top: 30rem;
margin-top: 20rem;
&.hide {
margin-bottom: -90rem;

View File

@@ -4,8 +4,6 @@ import Toolbar from "@/components/Toolbar/Toolbar.vue"
import {onMounted, watch} from "vue";
import {usePracticeStore} from "@/stores/practice.ts";
import Footer from "@/components/Practice/Footer.vue";
import TypeWord from "@/components/Practice/TypeWord.vue";
import TypeArticle from "@/components/Practice/TypeArticle.vue";
import {useBaseStore} from "@/stores/base.ts";
import {$ref} from "vue/macros";
import Statistics from "@/components/Practice/Statistics.vue";
@@ -17,14 +15,13 @@ import {useRuntimeStore} from "@/stores/runtime.ts";
import {renewSectionTexts, renewSectionTranslates} from "@/hooks/translate.ts";
import {MessageBox} from "@/utils/MessageBox.tsx";
import EditSingleArticleModal from "@/components/Article/EditSingleArticleModal.vue";
import PracticeArticle from "@/components/Practice/PracticeArticle/PracticeArticle.vue";
const practiceStore = usePracticeStore()
const store = useBaseStore()
const settingStore = useSettingStore()
const runtimeStore = useRuntimeStore()
let showEditArticle = $ref(false)
let editArticle = $ref<Article>(cloneDeep(DefaultArticle))
watch(practiceStore, () => {
if (practiceStore.inputWordNumber < 1) {
@@ -66,75 +63,9 @@ watch([
function getCurrentPractice() {
// console.log('store.currentDict',store.currentDict)
if (store.isArticle) {
// return
let currentArticle = store.currentDict.articles[store.currentDict.chapterIndex]
let tempArticle = {...DefaultArticle, ...currentArticle}
console.log('article', tempArticle)
if (tempArticle.sections.length) {
articleData.article = tempArticle
} else {
if (tempArticle.useTranslateType === TranslateType.none) {
renewSectionTexts(tempArticle)
articleData.article = tempArticle
} else {
if (tempArticle.useTranslateType === TranslateType.custom) {
if (tempArticle.textCustomTranslate.trim()) {
if (tempArticle.textCustomTranslateIsFormat) {
renewSectionTexts(tempArticle)
renewSectionTranslates(tempArticle, tempArticle.textCustomTranslate)
articleData.article = tempArticle
} else {
//说明有本地翻译,但是没格式化成一行一行的
MessageBox.confirm('检测到存在本地翻译,但未格式化,是否进行编辑?',
'提示',
() => {
editArticle = tempArticle
showEditArticle = true
},
() => {
renewSectionTexts(tempArticle)
tempArticle.useTranslateType = TranslateType.none
store.currentDict.articles[store.currentDict.chapterIndex] = articleData.article = tempArticle
},
{
confirmButtonText: '去编辑',
cancelButtonText: '不需要翻译',
})
}
} else {
//没有本地翻译
MessageBox.confirm(
'没有本地翻译,是否进行编辑?',
'提示',
() => {
editArticle = tempArticle
showEditArticle = true
},
() => {
renewSectionTexts(tempArticle)
tempArticle.useTranslateType = TranslateType.none
store.currentDict.articles[store.currentDict.chapterIndex] = articleData.article = tempArticle
},
{
confirmButtonText: '去编辑',
cancelButtonText: '不需要翻译',
})
}
}
if (tempArticle.useTranslateType === TranslateType.network) {
renewSectionTexts(tempArticle)
renewSectionTranslates(tempArticle, tempArticle.textNetworkTranslate)
store.currentDict.articles[store.currentDict.chapterIndex] = articleData.article = tempArticle
}
}
}
} else {
wordData.words = cloneDeep(store.chapter)
wordData.index = 0
console.log('wordData', wordData)
}
wordData.words = cloneDeep(store.chapter)
wordData.index = 0
console.log('wordData', wordData)
}
onMounted(() => {
@@ -161,18 +92,6 @@ function next() {
// repeat()
}
function saveArticle(val: Article) {
console.log('saveArticle', val)
showEditArticle = false
// articleData.article = cloneDeep(store.currentDict.articles[store.currentDict.chapterIndex])
store.currentDict.articles[store.currentDict.chapterIndex] = articleData.article = val
}
function edit(val: Article) {
editArticle = val
showEditArticle = true
}
function test() {
MessageBox.confirm(
'2您选择了“本地翻译”但译文内容却为空白是否修改为“不需要翻译”并保存?',
@@ -189,21 +108,7 @@ function test() {
<div class="practice">
<Toolbar/>
<!-- <BaseButton @click="test">test</BaseButton>-->
<TypeArticle
v-if="store.isArticle"
:article="articleData.article"
:sectionIndex="articleData.sectionIndex"
:sentenceIndex="articleData.sentenceIndex"
:wordIndex="articleData.wordIndex"
:stringIndex="articleData.stringIndex"
@next="next"
@edit="edit"
/>
<TypeWord
v-else
:words="wordData.words"
:index="wordData.index"
/>
<PracticeArticle v-if="store.isArticle"/>
<Footer/>
</div>
<Statistics
@@ -211,11 +116,6 @@ function test() {
@repeat="repeat"
@next="next"
/>
<EditSingleArticleModal
v-model="showEditArticle"
:article="editArticle"
@save="saveArticle"
/>
</template>
<style scoped lang="scss">
@@ -224,7 +124,7 @@ function test() {
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
justify-content: space-between;
align-items: center;
}

View File

@@ -1,24 +1,197 @@
<script setup lang="ts">
import {$ref} from "vue/macros";
import TypingArticle from "@/components/Practice/PracticeArticle/TypingArticle.vue";
import TypingArticle from "./TypingArticle.vue";
import {Article, DefaultArticle, TranslateType} from "@/types.ts";
import {cloneDeep} from "lodash-es";
import TypingWord from "@/components/Practice/PracticeWord/TypingWord.vue";
import ArticlePanel from "./ArticlePanel.vue";
import {onMounted, watch} from "vue";
import {renewSectionTexts, renewSectionTranslates} from "@/hooks/translate.ts";
import {MessageBox} from "@/utils/MessageBox.tsx";
import {useBaseStore} from "@/stores/base.ts";
import EditSingleArticleModal from "@/components/Article/EditSingleArticleModal.vue";
const store = useBaseStore()
let tabIndex = $ref(0)
let wordData = $ref({
words: [],
index: -1
})
let index = $ref(0)
let articleData = $ref({
article: cloneDeep(DefaultArticle),
sectionIndex: 0,
sentenceIndex: 0,
wordIndex: 0,
stringIndex: 0,
})
let showEditArticle = $ref(false)
let editArticle = $ref<Article>(cloneDeep(DefaultArticle))
watch([
() => store.current.index,
() => store.load,
() => store.current.dictType,
() => store.currentDict.chapterIndex,
], n => {
console.log('n', n)
getCurrentPractice()
})
onMounted(() => {
getCurrentPractice()
})
function getCurrentPractice() {
// console.log('store.currentDict',store.currentDict)
// return
if (!store.currentDict.articles.length) return
let currentArticle = store.currentDict.articles[store.currentDict.chapterIndex]
let tempArticle = {...DefaultArticle, ...currentArticle}
console.log('article', tempArticle)
if (tempArticle.sections.length) {
articleData.article = tempArticle
} else {
if (tempArticle.useTranslateType === TranslateType.none) {
renewSectionTexts(tempArticle)
articleData.article = tempArticle
} else {
if (tempArticle.useTranslateType === TranslateType.custom) {
if (tempArticle.textCustomTranslate.trim()) {
if (tempArticle.textCustomTranslateIsFormat) {
renewSectionTexts(tempArticle)
renewSectionTranslates(tempArticle, tempArticle.textCustomTranslate)
articleData.article = tempArticle
} else {
//说明有本地翻译,但是没格式化成一行一行的
MessageBox.confirm('检测到存在本地翻译,但未格式化,是否进行编辑?',
'提示',
() => {
editArticle = tempArticle
showEditArticle = true
},
() => {
renewSectionTexts(tempArticle)
tempArticle.useTranslateType = TranslateType.none
store.currentDict.articles[store.currentDict.chapterIndex] = articleData.article = tempArticle
},
{
confirmButtonText: '去编辑',
cancelButtonText: '不需要翻译',
})
}
} else {
//没有本地翻译
MessageBox.confirm(
'没有本地翻译,是否进行编辑?',
'提示',
() => {
editArticle = tempArticle
showEditArticle = true
},
() => {
renewSectionTexts(tempArticle)
tempArticle.useTranslateType = TranslateType.none
store.currentDict.articles[store.currentDict.chapterIndex] = articleData.article = tempArticle
},
{
confirmButtonText: '去编辑',
cancelButtonText: '不需要翻译',
})
}
}
if (tempArticle.useTranslateType === TranslateType.network) {
renewSectionTexts(tempArticle)
renewSectionTranslates(tempArticle, tempArticle.textNetworkTranslate)
store.currentDict.articles[store.currentDict.chapterIndex] = articleData.article = tempArticle
}
}
}
}
function saveArticle(val: Article) {
console.log('saveArticle', val)
showEditArticle = false
// articleData.article = cloneDeep(store.currentDict.articles[store.currentDict.chapterIndex])
store.currentDict.articles[store.currentDict.chapterIndex] = articleData.article = val
}
function edit(val: Article) {
editArticle = val
showEditArticle = true
}
</script>
<template>
<div class="swiper-wrapper content">
<div class="swiper-list" :class="`step${tabIndex}`">
<div class="swiper-item">
<TypingArticle/>
</div>
<div class="swiper-item">
<TypingArticle/>
<div class="practice-article">
<div class="swiper-wrapper content">
<div class="swiper-list" :class="`step${tabIndex}`">
<div class="swiper-item">
<TypingArticle
:article="articleData.article"
/>
</div>
<div class="swiper-item">
<TypingWord
:words="wordData.words"
:index="wordData.index"
v-if="tabIndex === 1"
/>
</div>
</div>
</div>
<div class="panel-wrapper">
<ArticlePanel :list="[]" v-model:index="index"/>
</div>
<EditSingleArticleModal
v-model="showEditArticle"
:article="editArticle"
@save="saveArticle"
/>
</div>
</template>
<style scoped lang="scss">
@import "@/assets/css/style.scss";
$article-width: 50vw;
.swiper-wrapper {
height: 100%;
overflow: hidden;
.swiper-list {
transition: transform .3s;
height: 200%;
.swiper-item {
height: 50%;
overflow: auto;
display: flex;
}
}
.step1 {
transform: translate3d(0, -50%, 0);
}
}
.practice-article {
flex: 1;
overflow: hidden;
width: $article-width;
}
.panel-wrapper {
position: fixed;
left: 0;
top: 10rem;
z-index: 1;
margin-left: calc(50% + ($article-width / 2) + $space);
height: calc(100% - 20rem);
}
</style>

View File

@@ -1,10 +1,9 @@
<script setup lang="ts">
import {computed, nextTick, onMounted, watch, watchEffect} from "vue"
import {computed, nextTick, onMounted, watch} from "vue"
import {$computed, $ref} from "vue/macros";
import {Article, ArticleWord, DefaultArticle, DisplayStatistics, ShortKeyMap, Word} from "@/types";
import {useBaseStore} from "@/stores/base";
import {usePracticeStore} from "@/stores/practice.ts";
import TypeWord from "@/components/Practice/TypeWord.vue";
import {useSettingStore} from "@/stores/setting.ts";
import {usePlayBeep, usePlayCorrect, usePlayKeyboardAudio, usePlayWordAudio} from "@/hooks/sound.ts";
import {useOnKeyboardEventListener} from "@/hooks/event.ts";
@@ -13,15 +12,13 @@ import {emitter, EventKey} from "@/utils/eventBus.ts";
import Tooltip from "@/components/Tooltip.vue";
import IconWrapper from "@/components/IconWrapper.vue";
import {Icon} from "@iconify/vue";
import WordPanel from "@/components/Practice/WordPanel.vue";
import ArticlePanel from "@/components/Practice/ArticlePanel.vue";
interface IProps {
article: Article,
sectionIndex: number,
sentenceIndex: number,
wordIndex: number,
stringIndex: number,
sectionIndex?: number,
sentenceIndex?: number,
wordIndex?: number,
stringIndex?: number,
}
const props = withDefaults(defineProps<IProps>(), {
@@ -428,49 +425,44 @@ function toggleCollect() {
}
}
let index = $ref(0)
</script>
<template>
<div class="typing-wrapper">
<div class="swiper-wrapper content">
<div class="swiper-list" :class="`step${tabIndex}`">
<div class="swiper-item">
<div class="article-wrapper">
<header>
<div class="title">{{ props.article.title }}</div>
<div class="titleTranslate" v-if="settingStore.translate">{{ props.article.titleTranslate }}</div>
<div class="options">
<Tooltip title="编辑(快捷键Ctrl + E)">
<IconWrapper>
<Icon icon="tabler:edit" class="menu"
@click="emit('edit',props.article)"/>
</IconWrapper>
</Tooltip>
<Tooltip title="忽略(快捷键:`)">
<IconWrapper>
<Icon icon="fluent:delete-20-regular" class="menu"
@click="tabIndex = 1"/>
</IconWrapper>
</Tooltip>
<Tooltip title="收藏(快捷键Enter)">
<IconWrapper>
<Icon :icon="`ph:star${collectIndex > -1?'-fill':''}`" class="menu"
@click="toggleCollect"/>
</IconWrapper>
</Tooltip>
<Tooltip title="跳过(快捷键Tab)">
<IconWrapper>
<Icon icon="icon-park-outline:go-ahead" class="menu"
@click="emit('next')"/>
</IconWrapper>
</Tooltip>
</div>
</header>
<div class="article-content" ref="articleWrapperRef">
<article>
<div class="section"
v-for="(section,indexI) in props.article.sections">
<div class="typing-article">
<header>
<div class="title">{{ props.article.title }}</div>
<div class="titleTranslate" v-if="settingStore.translate">{{ props.article.titleTranslate }}</div>
<div class="options">
<Tooltip title="编辑(快捷键Ctrl + E)">
<IconWrapper>
<Icon icon="tabler:edit" class="menu"
@click="emit('edit',props.article)"/>
</IconWrapper>
</Tooltip>
<Tooltip title="忽略(快捷键:`)">
<IconWrapper>
<Icon icon="fluent:delete-20-regular" class="menu"
@click="tabIndex = 1"/>
</IconWrapper>
</Tooltip>
<Tooltip title="收藏(快捷键Enter)">
<IconWrapper>
<Icon :icon="`ph:star${collectIndex > -1?'-fill':''}`" class="menu"
@click="toggleCollect"/>
</IconWrapper>
</Tooltip>
<Tooltip title="跳过(快捷键Tab)">
<IconWrapper>
<Icon icon="icon-park-outline:go-ahead" class="menu"
@click="emit('next')"/>
</IconWrapper>
</Tooltip>
</div>
</header>
<div class="article-content" ref="articleWrapperRef">
<article>
<div class="section"
v-for="(section,indexI) in props.article.sections">
<span class="sentence"
:class="[
sectionIndex === indexI && sentenceIndex === indexJ && settingStore.dictation
@@ -517,35 +509,19 @@ let index = $ref(0)
</span>
</span>
</span>
</div>
</article>
<div class="translate" v-show="settingStore.translate">
<template v-for="(v,i) in props.article.sections">
<div class="row"
:class="`translate${i+'-'+j}`"
v-for="(item,j) in v">
<span class="space"></span>
<Transition name="fade">
<span class="text" v-if="item.translate">{{ item.translate }}</span>
</Transition>
</div>
</template>
</div>
</div>
</div>
</article>
<div class="translate" v-show="settingStore.translate">
<template v-for="(v,i) in props.article.sections">
<div class="row"
:class="`translate${i+'-'+j}`"
v-for="(item,j) in v">
<span class="space"></span>
<Transition name="fade">
<span class="text" v-if="item.translate">{{ item.translate }}</span>
</Transition>
</div>
<Teleport to="body">
<div class="panel-wrapper">
<ArticlePanel :list="[]" v-model:index="index"/>
</div>
</Teleport>
</div>
<div class="swiper-item">
<TypeWord
:words="wordData.words"
:index="wordData.index"
v-if="tabIndex === 1"
/>
</div>
</template>
</div>
</div>
</div>
@@ -560,105 +536,93 @@ let index = $ref(0)
}
$article-width: 1000px;
.typing-wrapper {
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;
.typing-article {
.content {
width: $article-width;
}
header {
word-wrap: break-word;
position: relative;
padding: 15rem 0;
.article-wrapper {
header {
word-wrap: break-word;
position: relative;
padding: 15rem 0;
.title {
text-align: center;
color: rgba(gray, .8);
font-size: 36rem;
font-weight: 500;
word-spacing: 3rem;
//opacity: 0;
}
.titleTranslate {
@extend .title;
font-size: 20rem;
}
.options {
position: absolute;
right: 20rem;
top: 0;
display: flex;
gap: 10rem;
font-size: 18rem;
}
}
.article-content {
position: relative;
.title {
text-align: center;
color: rgba(gray, .8);
font-size: 36rem;
font-weight: 500;
word-spacing: 3rem;
//opacity: 0;
}
article {
//height: 100%;
font-size: 24rem;
line-height: 2.5;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
color: gray;
word-break: keep-all;
word-wrap: break-word;
white-space: pre-wrap;
padding-top: 20rem;
.section {
margin-bottom: $space;
.sentence {
transition: all .3s;
&.dictation {
letter-spacing: 3rem;
}
}
.word {
display: inline-block;
}
}
.titleTranslate {
@extend .title;
font-size: 20rem;
}
.translate {
pointer-events: none;
.options {
position: absolute;
right: 20rem;
top: 0;
left: 0;
height: 100%;
width: 100%;
display: flex;
gap: 10rem;
font-size: 18rem;
color: gray;
line-height: 3.5;
letter-spacing: 3rem;
//display: none;
}
}
.row {
position: absolute;
left: 0;
width: 100%;
opacity: 0;
.article-content {
position: relative;
//opacity: 0;
}
.space {
transition: all .3s;
display: inline-block;
article {
//height: 100%;
font-size: 24rem;
line-height: 2.5;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
color: gray;
word-break: keep-all;
word-wrap: break-word;
white-space: pre-wrap;
padding-top: 20rem;
.section {
margin-bottom: $space;
.sentence {
transition: all .3s;
&.dictation {
letter-spacing: 3rem;
}
}
.word {
display: inline-block;
}
}
}
.translate {
pointer-events: none;
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
font-size: 18rem;
color: gray;
line-height: 3.5;
letter-spacing: 3rem;
//display: none;
.row {
position: absolute;
left: 0;
width: 100%;
opacity: 0;
.space {
transition: all .3s;
display: inline-block;
}
}
}
@@ -713,27 +677,6 @@ $article-width: 1000px;
}
}
.swiper-wrapper {
height: 100%;
width: 100%;
overflow: hidden;
.swiper-list {
transition: transform .3s;
height: 200%;
.swiper-item {
height: 50%;
overflow: auto;
display: flex;
}
}
.step1 {
transform: translate3d(0, -50%, 0);
}
}
@keyframes underline {
0%, 100% {
border-left: 1.3rem solid black;
@@ -743,12 +686,4 @@ $article-width: 1000px;
}
}
.panel-wrapper {
position: fixed;
left: 0;
top: 10rem;
z-index: 1;
margin-left: calc(50% + ($article-width / 2) + $space);
height: calc(100% - 20rem);
}
</style>

View File

@@ -2,8 +2,7 @@
import {onMounted, watch} from "vue"
import {$computed, $ref} from "vue/macros"
import {useBaseStore} from "@/stores/base.ts"
import {DictType, DisplayStatistics, ShortKeyMap, Word} from "../../types";
import BaseButton from "@/components/BaseButton.vue";
import {DictType, DisplayStatistics, ShortKeyMap, Word} from "../../../types";
import {emitter, EventKey} from "@/utils/eventBus.ts"
import {cloneDeep} from "lodash-es"
import {usePracticeStore} from "@/stores/practice.ts"
@@ -13,7 +12,7 @@ import {useOnKeyboardEventListener} from "@/hooks/event.ts";
import {Icon} from "@iconify/vue";
import VolumeIcon from "@/components/VolumeIcon.vue";
import Tooltip from "@/components/Tooltip.vue";
import WordPanel from "@/components/Practice/WordPanel.vue";
import WordPanel from "./WordPanel.vue";
import IconWrapper from "@/components/IconWrapper.vue";
interface IProps {

View File

@@ -63,9 +63,9 @@ export const useBaseStore = defineStore('base', {
}
],
current: {
// dictType: DictType.word,
dictType: DictType.article,
index: 0,
dictType: DictType.word,
// dictType: DictType.article,
index: 1,
editIndex: 0,
repeatNumber: 0,
},
@@ -192,9 +192,12 @@ export const useBaseStore = defineStore('base', {
DictType.article,
DictType.customArticle,
].includes(this.current.dictType)) {
console.log(1,this.currentDict)
if (!this.currentDict.articles.length) {
console.log(2)
let r = await fetch(`./dicts/${this.currentDict.language}/${this.currentDict.type}/${this.currentDict.translateLanguage}/${this.currentDict.url}`)
r.json().then((v: any[]) => {
console.log(3)
this.currentDict.articles = cloneDeep(v.map(v => {
v.id = uuidv4()
return v