Merge branch 'dev'

This commit is contained in:
zyronon
2023-12-05 02:06:17 +08:00
38 changed files with 577 additions and 552 deletions

View File

@@ -104,7 +104,7 @@
"text": "On Wednesday evening, we went to the Town Hall. It was the last day of the year and a large crowd of people had gathered under the Town Hall clock. It would strike twelve in twenty minutes' time. Fifteen minutes passed and then, at five to twelve, the clock stopped. The big minute hand did not move. We waited and waited, but nothing happened. Suddenly someone shouted. 'It's two minutes past twelve! The clock has stopped!' I looked at my watch. It was true. The big clock refused to welcome the New Year. At that moment, everybody began to laugh and sing.\n",
"textCustomTranslate": "星期三的晚上,我们去了市政厅。\n 那是一年的最后一天,一大群人聚集在市政厅的大钟下面。\n再过20分钟大钟将敲响12下。\n15分钟过去了而就在11点55分时大钟停了。\n那根巨大的分针不动了。\n 我们等啊等啊,可情况没有变化。\n突然有人喊道“已经12点零2分了\n那钟已经停了”\n我看了一下我的手表\n果真如此。\n那座大钟不愿意迎接新年。\n此时大家已经笑了起来同时唱起了歌。",
"textNetworkTranslate": "",
"textCustomTranslateIsFormat": false,
"textCustomTranslateIsFormat": true,
"useTranslateType": "custom",
"newWords": [],
"id": "UydP2M"

View File

@@ -44,16 +44,31 @@ watch(settingStore.$state, (n) => {
//检测几个特定词典
watch(store.collect.originWords, (n) => {
store.collect.words = cloneDeep(n)
store.collect.chapterWords = [store.collect.words]
if (n.length === 0) {
store.collect.words = []
store.collect.chapterWords = []
} else {
store.collect.words = cloneDeep(n)
store.collect.chapterWords = [store.collect.words]
}
})
watch(store.simple.originWords, (n) => {
store.simple.words = cloneDeep(n)
store.simple.chapterWords = [store.simple.words]
if (n.length === 0) {
store.simple.words = []
store.simple.chapterWords = []
} else {
store.simple.words = cloneDeep(n)
store.simple.chapterWords = [store.simple.words]
}
})
watch(store.wrong.originWords, (n) => {
store.wrong.words = cloneDeep(n)
store.wrong.chapterWords = [store.wrong.words]
if (n.length === 0) {
store.wrong.words = []
store.wrong.chapterWords = []
} else {
store.wrong.words = cloneDeep(n)
store.wrong.chapterWords = [store.wrong.words]
}
})
async function init() {

View File

@@ -19,7 +19,7 @@
--color-header-bg: white;
--color-tooltip-bg: white;
--color-tooltip-shadow: #bbbbbb;
--color-font-1: black;
--color-font-1: rgb(91, 91, 91);
--color-font-2: rgb(46, 46, 46);
--color-font-3: rgb(75, 85, 99);
--color-font-active-1: white;
@@ -27,18 +27,24 @@
--color-main-active: rgb(12, 140, 233);
--color-scrollbar: rgb(147, 173, 227);
--color-gray: gray;
--color-sub-gray: #c0bfbf;
--practice-wrapper-padding-right: 1px;
--practice-wrapper-translateX: 1px;
--article-width: 50vw;
--toolbar-width: 700rem;
--toolbar-height: 54rem;
--panel-width: 400rem;
--space: 20rem;
--panel-margin-left: calc(50% - var(--practice-wrapper-padding-right) / 2 + var(--toolbar-width) / 2 + 24rem);
--panel-margin-left: calc(50% - var(--practice-wrapper-translateX) / 2 + var(--toolbar-width) / 2 + 24rem);
--article-panel-margin-left: calc(50% - var(--practice-wrapper-translateX) / 2 + var(--article-width) / 2 + 24rem);
--anim-time: 0.5s;
--color-input-bg: white;
--color-input-icon: #d3d4d7;
--color-textarea-bg: white;
--font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
--word-font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
}
@@ -65,22 +71,27 @@ html.dark {
--color-font-3: rgba(255, 255, 255, 0.3);
--color-gray: #bebebe;
--color-sub-gray: #383737;
--color-scrollbar: rgb(59, 87, 138);
--color-scrollbar: rgb(77, 78, 81);
--color-main-active: rgb(147, 173, 227);
--color-scrollbar: rgb(92, 93, 94);
--color-input-bg: rgba(14, 18, 23, 1);
--color-input-icon: #383737;
--color-textarea-bg: rgb(43, 45, 48);
}
@media (max-width: 1680px) {
:root {
--practice-wrapper-padding-right: 25vw;
--practice-wrapper-translateX: -12vw;
--toolbar-width: 40vw;
--article-width: 60vw;
--panel-width: 380rem;
--toolbar-height: 48rem;
--panel-margin-left: calc(50% - var(--practice-wrapper-padding-right) / 2 + var(--toolbar-width) / 2 + 9vw);
--panel-margin-left: calc(50vw + var(--practice-wrapper-translateX) + var(--toolbar-width) / 2 + 5vw);
--article-panel-margin-left: calc(50% + var(--practice-wrapper-translateX) + var(--article-width) / 2 + 48rem);
}
.footer {
.bottom {
@@ -100,11 +111,13 @@ html.dark {
@media (max-width: 1366px) {
:root {
--space: 10rem;
--practice-wrapper-padding-right: 30vw;
--practice-wrapper-translateX: -22vw;
--article-width: 53vw;
--panel-width: 30vw;
--toolbar-width: 50vw;
--toolbar-height: 40rem;
--panel-margin-left: calc(50% - var(--practice-wrapper-padding-right) / 2 + var(--toolbar-width) / 2 + 9vw);
--panel-margin-left: calc(50vw + var(--practice-wrapper-translateX) + var(--toolbar-width) / 2 + 14vw);
--article-panel-margin-left: calc(50% + var(--practice-wrapper-translateX) + var(--article-width) / 2 + 12vw);
}
.footer {
@@ -174,7 +187,7 @@ a {
min-height: 20rem;
width: 100%;
box-sizing: border-box;
background: var(--color-item-bg);
background: var(--color-textarea-bg);
&:focus {
border: 1px solid var(--color-main-active);
@@ -429,5 +442,4 @@ footer {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 10rem;
}

View File

@@ -43,7 +43,7 @@ const groupByTranslateLanguage = $computed(() => {
data = groupBy(articleList, 'translateLanguage')
} else if (currentLanguage === 'my') {
data = {
common: store.myDictList.concat([{name: '',} as any])
common: store.myDictList.concat([{id: '',} as any])
}
} else {
data = groupBy(groupByLanguage[currentLanguage], 'translateLanguage')
@@ -87,6 +87,7 @@ const groupedByCategoryAndTag = $computed(() => {
<DictList
@add="emit('add')"
@selectDict="e => emit('selectDict',e)"
:select-id="store.currentDict.id"
:list="groupByTranslateLanguage['common']"/>
</template>
<template v-else>
@@ -98,7 +99,7 @@ const groupedByCategoryAndTag = $computed(() => {
</div>
<DictGroup
v-for="item in groupedByCategoryAndTag"
:select-dict-name="runtimeStore.editDict.resourceId"
:select-id="store.currentDict.id"
@selectDict="e => emit('selectDict',e)"
:groupByTag="item[1]"
:category="item[0]"

View File

@@ -5,7 +5,6 @@
</template>
<style scoped lang="scss">
@import "@/assets/css/variable.scss";
$w: 22rem;
.icon-wrapper {
@@ -18,10 +17,10 @@ $w: 22rem;
border-radius: 3rem;
background: transparent;
transition: all .3s;
color: $main;
color: var(--color-main-active);
&:hover {
background: $main;
background: var(--color-main-active);
color: white;
}

View File

@@ -2,7 +2,8 @@
import {$ref} from "vue/macros";
import {Icon} from "@iconify/vue";
import Close from "@/components/icon/Close.vue";
import {useWindowClick} from "@/hooks/event.ts";
import {useDisableEventListener, useWindowClick} from "@/hooks/event.ts";
import {watch} from "vue";
defineProps<{
modelValue: string
@@ -17,6 +18,8 @@ useWindowClick((e: PointerEvent) => {
focus = inputEl.contains(e.target as any);
})
useDisableEventListener(() => focus)
</script>
<template>
@@ -30,7 +33,9 @@ useWindowClick((e: PointerEvent) => {
:value="modelValue"
@input="e=>$emit('update:modelValue',e.target.value)"
>
<Close @click="$emit('update:modelValue','')"/>
<transition name="fade">
<Close v-if="modelValue" @click="$emit('update:modelValue','')"/>
</transition>
</div>
</template>

View File

@@ -70,7 +70,7 @@ $w2: calc($w / 2);
.circle-detail {
transform-origin: $w2 $w2;
transform: rotate(-90deg);
stroke: $main;
stroke: var(--color-main-active);
}
}

View File

@@ -448,7 +448,7 @@ defineExpose({save, getEditArticle: () => cloneDeep(editArticle)})
border-radius: 8rem;
.section {
background: var(--color-item-bg);
background: var(--color-textarea-bg);
margin-bottom: 20rem;
padding: var(--space);
border-radius: 8rem;

View File

@@ -78,7 +78,7 @@ function checkDataChange() {
let r = await editArticleRef.save('save')
if (r) resolve(true)
},
() => void 0,
() => resolve(true),
)
}
} else {
@@ -90,7 +90,7 @@ function checkDataChange() {
let r = await editArticleRef.save('save')
if (r) resolve(true)
},
() => void 0,
() => resolve(true),
)
}
}

View File

@@ -4,6 +4,7 @@ import {Article, DefaultArticle} from "@/types.ts";
import {cloneDeep} from "lodash-es";
import Dialog from "@/components/dialog/Dialog.vue";
import EditArticle from "@/components/article/EditArticle.vue";
import {useDisableEventListener} from "@/hooks/event.ts";
interface IProps {
article?: Article
@@ -16,14 +17,18 @@ const props = withDefaults(defineProps<IProps>(), {
})
const emit = defineEmits<{
save: [val: Article]
'update:modelValue': [val: boolean]
}>()
useDisableEventListener(() => props.modelValue)
</script>
<template>
<Dialog
:header="false"
:model-value="props.modelValue"
@close="emit('update:modelValue',false)"
:full-screen="true"
>
<div class="wrapper">

View File

@@ -65,7 +65,7 @@ onUnmounted(() => {
display: flex;
gap: var(--space);
padding: var(--space);
color: var(--color-font-1);
.article-content {
flex: 1;

View File

@@ -35,6 +35,7 @@ let loading = $ref(false)
let show = $ref(false)
let chapterList2 = $ref([])
let chapterWordNumber = $ref(0)
let toggleLoading = $ref(false)
const activeId = $computed(() => {
if (dictIsArticle) {
@@ -104,22 +105,15 @@ function close() {
}
function changeDict() {
store.changeDict(runtimeStore.editDict)
close()
}
function clickEvent(e) {
console.log('e', e)
store.changeDict(runtimeStore.editDict)
ElMessage.success('切换成功')
}
const dictIsArticle = $computed(() => {
return isArticle(runtimeStore.editDict.type)
})
const chapterList = $computed(() => {
return dictIsArticle ? runtimeStore.editDict.articles.length : runtimeStore.editDict.chapterWords.length
})
function showAllWordModal() {
emitter.emit(EventKey.openWordListModal, {
title: runtimeStore.editDict.name,
@@ -128,7 +122,7 @@ function showAllWordModal() {
})
}
function resetChapterList(v) {
function resetChapterList(v: number) {
const temp = () => {
runtimeStore.editDict.chapterWordNumber = v
runtimeStore.editDict.chapterWords = chunk(runtimeStore.editDict.words, runtimeStore.editDict.chapterWordNumber)
@@ -148,7 +142,7 @@ function resetChapterList(v) {
}
}
function changeSort(v, notice: boolean = true) {
function changeSort(v: Sort, notice: boolean = true) {
const temp = () => {
runtimeStore.editDict.sort = v
if (v === Sort.normal) {
@@ -158,7 +152,7 @@ function changeSort(v, notice: boolean = true) {
} else {
runtimeStore.editDict.words = reverse(cloneDeep(runtimeStore.editDict.originWords))
}
resetChapterList()
resetChapterList(runtimeStore.editDict.chapterWordNumber)
notice && ElMessage.success('已重新排序')
}
if (runtimeStore.editDict.isCustom) {
@@ -179,16 +173,6 @@ function option(type: string) {
}, 500)
}
/**/
/* 单词修改相关*/
/**/
watch(() => step, v => {
if (v === 0) {
}
})
onMounted(() => {
emitter.on(EventKey.openDictModal, (type: 'detail' | 'list' | 'my') => {
if (type === "detail") {
@@ -206,14 +190,6 @@ onMounted(() => {
})
})
function addDict() {
show = false
setTimeout(() => {
router.push({path: '/dict', query: {type: 'addDict'}})
}, 500)
}
function showWordListModal(val: { item: Word, index: number }) {
emitter.emit(EventKey.openWordListModal, {
title: `${val.index + 1}`,
@@ -222,7 +198,7 @@ function showWordListModal(val: { item: Word, index: number }) {
})
}
function handleChangeArticleChapterIndex(val) {
function handleChangeArticleChapterIndex(val: any) {
let rIndex = runtimeStore.editDict.articles.findIndex(v => v.id === val.item.id)
if (rIndex > -1) {
runtimeStore.editDict.chapterIndex = rIndex
@@ -239,7 +215,7 @@ function handleChangeArticleChapterIndex(val) {
<div id="DictDialog">
<Slide :slide-count="2" :step="step">
<DictListPanel
@add="addDict"
@add="option('addDict')"
@select-dict="selectDict"
/>
<div class="dict-detail-page">
@@ -280,14 +256,16 @@ function handleChangeArticleChapterIndex(val) {
:title="`添加${dictIsArticle?'文章':'单词'}`"
/>
</div>
<div class="text">开始日期-</div>
<div class="text">花费时间-</div>
<div class="text">累积错误-</div>
<div class="text">进度
<el-progress :percentage="0"
:stroke-width="8"
:show-text="false"/>
</div>
<template v-if="false">
<div class="text">开始日期-</div>
<div class="text">花费时间-</div>
<div class="text">累积错误-</div>
<div class="text">进度
<el-progress :percentage="0"
:stroke-width="8"
:show-text="false"/>
</div>
</template>
</div>
<div class="center-column">
<div class="common-title">学习设置</div>
@@ -385,63 +363,52 @@ function handleChangeArticleChapterIndex(val) {
:title="`管理${dictIsArticle?'文章':'章节'}`"
/>
</div>
<template v-if="dictIsArticle">
<ArticleList
v-if="runtimeStore.editDict.articles.length"
:isActive="false"
v-loading="loading"
:show-border="true"
@title="(val:any) => emitter.emit(EventKey.openArticleContentModal,val.item)"
@click="handleChangeArticleChapterIndex"
:active-id="activeId"
:list="runtimeStore.editDict.articles">
<template v-slot:prefix="{item,index}">
<input type="radio" :checked="activeId === item.id">
</template>
</ArticleList>
<Empty v-else/>
</template>
<template v-else>
<BaseList
ref="chapterListRef"
v-if="chapterList2.length"
:list="chapterList2"
:show-border="true"
@click="(val:any) => runtimeStore.editDict.chapterIndex = val.index"
:active-index="runtimeStore.editDict.chapterIndex"
>
<template v-slot:prefix="{ item, index }">
<input type="radio" :checked="runtimeStore.editDict.chapterIndex === item.id">
</template>
<template v-slot="{ item, index }">
<div class="item-title" @click.stop="showWordListModal({item,index})">
<span>{{ item.id + 1 }}</span>&nbsp;&nbsp;&nbsp;
<span>{{ runtimeStore.editDict.chapterWords[item.id]?.length }}</span>
</div>
</template>
</BaseList>
<Empty v-else/>
</template>
<div class="list-content">
<template v-if="dictIsArticle">
<ArticleList
v-if="runtimeStore.editDict.articles.length"
:isActive="false"
v-loading="loading"
:show-border="true"
@title="(val:any) => emitter.emit(EventKey.openArticleContentModal,val.item)"
@click="handleChangeArticleChapterIndex"
:active-id="activeId"
:list="runtimeStore.editDict.articles">
<template v-slot:prefix="{item,index}">
<input type="radio" :checked="activeId === item.id">
</template>
</ArticleList>
<Empty v-else/>
</template>
<template v-else>
<BaseList
ref="chapterListRef"
v-if="chapterList2.length"
:list="chapterList2"
:show-border="true"
@click="(val:any) => runtimeStore.editDict.chapterIndex = val.index"
:active-index="runtimeStore.editDict.chapterIndex"
>
<template v-slot:prefix="{ item, index }">
<input type="radio" :checked="runtimeStore.editDict.chapterIndex === item.id">
</template>
<template v-slot="{ item, index }">
<div class="item-title" @click.stop="showWordListModal({item,index})">
<span>{{ item.id + 1 }}</span>&nbsp;&nbsp;&nbsp;
<span>{{ runtimeStore.editDict.chapterWords[item.id]?.length }}</span>
</div>
</template>
</BaseList>
<Empty v-else/>
</template>
</div>
<div class="footer">
<!-- <BaseButton @click="step = 0">导出</BaseButton>-->
<BaseButton @click="close">关闭</BaseButton>
<BaseButton :loading="toggleLoading" @click="changeDict">切换</BaseButton>
</div>
</div>
</div>
<div v-if="false" class="activity">
<ActivityCalendar
:data="[{ date: '2023-05-22', count: 5 }]"
:width="40"
:height="7"
:cellLength="16"
:cellInterval="8"
:fontSize="12"
:showLevelFlag="false"
:showWeekDayFlag="false"
:clickEvent="clickEvent"
/>
</div>
<div class="footer">
<!-- <BaseButton @click="step = 0">导出</BaseButton>-->
<BaseButton @click="close">关闭</BaseButton>
<BaseButton @click="changeDict">切换</BaseButton>
</div>
</div>
</div>
</Slide>
@@ -505,17 +472,21 @@ $header-height: 60rem;
display: flex;
position: relative;
.left-column {
overflow: auto;
flex: 6;
.column {
background: var(--color-second-bg);
color: var(--color-font-1);
display: flex;
flex-direction: column;
}
.left-column {
flex: 5;
gap: 10rem;
min-height: 100rem;
position: relative;
color: var(--color-font-1);
font-size: 14rem;
padding-right: var(--space);
@extend .column;
.name {
font-size: 24rem;
@@ -542,10 +513,7 @@ $header-height: 60rem;
.center-column {
overflow: auto;
flex: 7;
background: white;
border-radius: 10rem;
background: var(--color-second-bg);
color: var(--color-font-1);
@extend .column;
.setting {
.row {
@@ -575,58 +543,20 @@ $header-height: 60rem;
font-size: 13rem;
}
}
}
.right-column {
flex: 7;
border-radius: 10rem;
background: var(--color-second-bg);
color: var(--color-font-1);
display: flex;
flex-direction: column;
@extend .column;
.tabs {
.list-content {
flex: 1;
overflow: hidden;
display: flex;
margin-bottom: 10rem;
.tab {
font-size: 20rem;
color: var(--color-font-3);
flex: 1;
display: flex;
justify-content: center;
align-items: center;
span {
cursor: pointer;
border-bottom: 3px solid transparent;
padding-bottom: 10rem;
transition: all .3s;
}
&.active {
color: var(--color-font-1);
span {
border-bottom: 3px solid var(--color-main-active);
}
}
}
}
.scroll {
height: calc(100% - 45rem);
}
}
}
.activity {
display: flex;
justify-content: center;
}
.footer {
box-sizing: content-box;
display: flex;
@@ -634,7 +564,7 @@ $header-height: 60rem;
justify-content: flex-end;
gap: var(--space);
padding-right: var(--space);
margin-bottom: 20rem;
margin: var(--space) 0;
}
}
}

View File

@@ -6,6 +6,7 @@ import {onMounted, onUnmounted, watch} from "vue";
import {emitter, EventKey} from "@/utils/eventBus.ts";
import {useRuntimeStore} from "@/stores/runtime.ts";
import WordList from "@/components/list/WordList.vue";
import Empty from "@/components/Empty.vue";
let show = $ref(false)
let loading = $ref(false)
@@ -78,9 +79,11 @@ onUnmounted(() => {
:show-text="false"/>
</div>
<WordList
v-if="list.length"
class="word-list"
:list="list">
</WordList>
<Empty v-else/>
</div>
</Dialog>
</template>

View File

@@ -8,7 +8,7 @@ import DictList from "@/components/list/DictList.vue";
const props = defineProps<{
category: string,
groupByTag: any,
selectDictName: string
selectId: string
}>()
const emit = defineEmits<{
selectDict: [val: { dict: DictResource, index: number }]
@@ -37,7 +37,8 @@ watch(() => props.groupByTag, () => {
</div>
<DictList
@selectDict="e => emit('selectDict',e)"
:list="list" :select-dict-name="selectDictName"/>
:list="list"
:select-id="selectId"/>
</div>
</template>

View File

@@ -20,7 +20,7 @@ const emit = defineEmits<{
class="dict-item anim"
:class="active && 'active'"
>
<template v-if="dict.name">
<template v-if="dict.id">
<div class="top">
<div class="name">{{ dict.name }}</div>
<div class="desc">{{ dict.description }}</div>
@@ -109,15 +109,17 @@ const emit = defineEmits<{
position: absolute;
bottom: 0;
left: 0;
height: 50rem;
width: 50rem;
color: white; background-color: skyblue;
height: 55rem;
width: 55rem;
color: white;
//background-color: skyblue;
background-color: var(--color-main-active);
clip-path: polygon(0 10%, 0% 100%, 100% 100%);
font-size: 12rem;
display: flex;
justify-content: flex-start;
align-items: flex-end;
padding: 5rem;
padding: 3rem;
box-sizing: border-box;
}
}

View File

@@ -5,7 +5,7 @@ import DictItem from "@/components/list/DictItem.vue";
defineProps<{
list?: Dict[],
selectDictName?: string
selectId?: string
}>()
const emit = defineEmits<{
@@ -19,6 +19,7 @@ const emit = defineEmits<{
<template>
<div class="dict-list">
<DictItem v-for="(dict,index) in list"
:active="selectId === dict.id"
@click="emit('selectDict',{dict,index})"
@add="emit('add')"
:dict="dict"/>

View File

@@ -10,7 +10,6 @@ import {useSettingStore} from "@/stores/setting.ts";
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
import {getAudioFileUrl, useChangeAllSound, usePlayAudio, useWatchAllSound} from "@/hooks/sound.ts";
import {SoundFileOptions} from "@/utils/const.ts";
import {throttle} from "lodash-es";
const settingStore = useSettingStore()

View File

@@ -15,7 +15,7 @@ import {useSettingStore} from "@/stores/setting.ts";
import {usePracticeStore} from "@/stores/practice.ts";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {$ref} from "vue/macros";
import {ShortcutKey} from "@/types.ts";
import {DictType, ShortcutKey} from "@/types.ts";
import ChapterName from "@/components/toolbar/ChapterName.vue";
import {emitter, EventKey} from "@/utils/eventBus.ts";
@@ -69,7 +69,7 @@ watch(() => store.load, n => {
{{ store.currentDict.name }} {{ practiceStore.repeatNumber ? ' 复习错词' : '' }}
</div>
</Tooltip>
<ChapterName/>
<ChapterName v-if="store.currentDict.type === DictType.word"/>
<div class="info-text" v-if="practiceStore.repeatNumber">
复习错词
</div>

View File

@@ -273,7 +273,6 @@ export function getSplitTranslateText(article: string) {
export function isArticle(type: DictType): boolean {
return [
DictType.article,
DictType.customArticle
].includes(type)
}

View File

@@ -53,6 +53,7 @@ export function useWordOptions() {
if (rIndex > -1) {
store.wrong.originWords.splice(rIndex, 1)
}
store.wrong.length = store.wrong.originWords.length
}
function delSimpleWord(val: Word) {
@@ -60,6 +61,7 @@ export function useWordOptions() {
if (rIndex > -1) {
store.simple.originWords.splice(rIndex, 1)
}
store.simple.length = store.simple.originWords.length
}
return {
@@ -98,7 +100,6 @@ export async function checkDictHasTranslate(dict: Dict) {
let dictResourceUrl = `./dicts/${dict.language}/${dict.type}/${dict.translateLanguage}/${dict.url}`;
if ([
DictType.word,
DictType.customWord,
].includes(dict.type)) {
if (!dict.originWords.length) {
let r = await fetch(dictResourceUrl)
@@ -129,7 +130,6 @@ export async function checkDictHasTranslate(dict: Dict) {
if ([
DictType.article,
DictType.customArticle,
].includes(dict.type)) {
if (!dict.articles.length) {
let r = await fetch(dictResourceUrl)

View File

@@ -4,6 +4,7 @@ import {PronunciationApi} from "@/types.ts";
import beep from "@/assets/sound/beep.wav";
import correct from "@/assets/sound/correct.wav";
import {$ref} from "vue/macros";
import {SoundFileOptions} from "@/utils/const.ts";
export function useSound(audioSrcList?: string[], audioFileLength?: number) {
let audioList: HTMLAudioElement[] = $ref([])
@@ -43,6 +44,9 @@ export function usePlayKeyboardAudio() {
const {play, setAudio} = useSound()
watchEffect(() => {
if (!SoundFileOptions.find(v => v.label === settingStore.keyboardSoundFile)) {
settingStore.keyboardSoundFile = '机械键盘2'
}
let urlList = getAudioFileUrl(settingStore.keyboardSoundFile)
setAudio(urlList, urlList.length === 1 ? 3 : 1)
})
@@ -128,7 +132,7 @@ export function usePlayAudio(url: string) {
}
export function getAudioFileUrl(name: string) {
if (name === '机械') {
if (name === '机械键盘') {
return [
`./sound/key-sounds/jixie/机械0.mp3`,
`./sound/key-sounds/jixie/机械1.mp3`,

View File

@@ -80,6 +80,7 @@ async function getDictDetail(val: {
runtimeStore.editDict.length = runtimeStore.editDict.articles.length
}
}
loading = false
}

View File

@@ -30,7 +30,7 @@ const DefaultDictForm = {
tags: [],
translateLanguage: 'zh-CN',
language: 'en',
type: DictType.customWord
type: DictType.word
}
let dictForm: any = $ref(cloneDeep(DefaultDictForm))
const dictFormRef = $ref<FormInstance>()
@@ -156,9 +156,9 @@ onMounted(() => {
</el-select>
</el-form-item>
<el-form-item label="类型">
<el-select v-model="dictForm.type" placeholder="请选择选项">
<el-option label="单词" :value="DictType.customWord"/>
<el-option label="文章" :value="DictType.customArticle"/>
<el-select v-model="dictForm.type" placeholder="请选择选项" :disabled="dictForm.id">
<el-option label="单词" :value="DictType.word"/>
<el-option label="文章" :value="DictType.article"/>
</el-select>
</el-form-item>
<div class="flex-center">

View File

@@ -116,12 +116,12 @@ onUnmounted(() => {
align-items: center;
gap: 5rem;
width: 80rem;
color: gray;
.line {
height: 1px;
width: 100%;
//background: gainsboro;
background: var(--color-font-1);
background: var(--color-sub-gray);
}
}
}

View File

@@ -1,8 +1,8 @@
<script setup lang="ts">
import {useBaseStore} from "@/stores/base.ts"
import {$ref} from "vue/macros"
import {computed, onMounted, provide, watch} from "vue"
import {$computed, $ref} from "vue/macros"
import {computed, onMounted, onUnmounted, provide, watch} from "vue"
import {Dict, DictType, ShortcutKey} from "@/types.ts"
import PopConfirm from "@/components/PopConfirm.vue"
import BaseButton from "@/components/BaseButton.vue";
@@ -20,6 +20,7 @@ import {useRuntimeStore} from "@/stores/runtime.ts";
import {cloneDeep} from "lodash-es";
import WordList from "@/components/list/WordList.vue";
import ArticleList from "@/components/list/ArticleList.vue";
import Slide from "@/components/Slide.vue";
const router = useRouter()
const store = useBaseStore()
@@ -36,8 +37,8 @@ watch(() => settingStore.showPanel, n => {
let practiceType = $ref(DictType.word)
function changeIndex(i: number, dict: Dict) {
store.changeDict(dict, dict.chapterIndex, i, practiceType)
function changeIndex(dict: Dict) {
store.changeDict(dict, practiceType)
}
onMounted(() => {
@@ -46,6 +47,10 @@ onMounted(() => {
})
})
onUnmounted(() => {
emitter.off(EventKey.changeDict)
})
const {
delWrongWord,
delSimpleWord,
@@ -53,7 +58,6 @@ const {
} = useWordOptions()
const {
isArticleCollect,
toggleArticleCollect
} = useArticleOptions()
@@ -67,6 +71,19 @@ function addSimple() {
router.push({path: '/dict', query: {type: 'addWordOrArticle'}})
}
const showCollectToggleButton = $computed(() => {
if (store.currentDict.type === DictType.collect) {
if (store.current.practiceType !== practiceType) {
return (practiceType === DictType.word && store.collect.words.length) ||
(practiceType === DictType.article && store.collect.articles.length)
}
} else {
return (practiceType === DictType.word && store.collect.words.length) ||
(practiceType === DictType.article && store.collect.articles.length)
}
return false
})
</script>
<template>
<Transition name="fade">
@@ -87,140 +104,126 @@ function addSimple() {
<div class="tab" :class="tabIndex === 3 && 'active'" @click="tabIndex = 3">{{ store.wrong.name }}</div>
</div>
</header>
<div class="slide">
<div class="slide-list" :class="`step${tabIndex}`">
<div class="slide-item">
<slot :active="tabIndex === 0 && settingStore.showPanel"></slot>
</div>
<div class="slide-item">
<div class="panel-page-item">
<div class="list-header">
<div class="left">
<el-radio-group v-model="practiceType">
<el-radio-button border :label="DictType.word">单词</el-radio-button>
<el-radio-button border :label="DictType.article">文章</el-radio-button>
</el-radio-group>
<div class="dict-name" v-if="practiceType === DictType.word && store.collect.words.length">
{{ store.collect.words.length }}个单词
</div>
<div class="dict-name" v-if="practiceType === DictType.article && store.collect.articles.length">
{{ store.collect.articles.length }}篇文章
</div>
<Tooltip title="添加">
<IconWrapper>
<Icon icon="fluent:add-12-regular" @click="addCollect"/>
</IconWrapper>
</Tooltip>
<Slide :slide-count="4" :step="tabIndex">
<div class="slide-item">
<slot :active="tabIndex === 0 && settingStore.showPanel"></slot>
</div>
<div class="slide-item">
<div class="panel-page-item">
<div class="list-header">
<div class="left">
<el-radio-group v-model="practiceType">
<el-radio-button border :label="DictType.word">单词</el-radio-button>
<el-radio-button border :label="DictType.article">文章</el-radio-button>
</el-radio-group>
<div class="dict-name" v-if="practiceType === DictType.word && store.collect.words.length">
{{ store.collect.words.length }}个单词
</div>
<template v-if="store.currentDict.type !== DictType.collect &&
(
( practiceType === DictType.word && store.collect.words.length) ||
( practiceType === DictType.article && store.collect.articles.length)
)">
<PopConfirm
:title="`确认切换?`"
@confirm="changeIndex(0,store.collect)"
>
<BaseButton size="small">切换</BaseButton>
</PopConfirm>
</template>
<div class="dict-name" v-if="practiceType === DictType.article && store.collect.articles.length">
{{ store.collect.articles.length }}篇文章
</div>
<BaseIcon icon="fluent:add-12-regular" title="添加" @click="addCollect"/>
</div>
<template v-if="practiceType === DictType.word">
<WordList
v-if="store.collect.words.length"
class="word-list"
:list="store.collect.words">
<template v-slot:suffix="{item,index}">
<BaseIcon
class="del"
@click="toggleWordCollect(item)"
title="移除"
icon="solar:trash-bin-minimalistic-linear"/>
</template>
</WordList>
<Empty v-else/>
</template>
<template v-else>
<ArticleList
v-if="store.collect.articles.length"
v-model:list="store.collect.articles">
<template v-slot:suffix="{item,index}">
<BaseIcon
class="del"
@click="toggleArticleCollect(item)"
title="移除"
icon="solar:trash-bin-minimalistic-linear"/>
</template>
</ArticleList>
<Empty v-else/>
<template v-if="showCollectToggleButton">
<PopConfirm
:title="`确认切换?`"
@confirm="changeIndex( store.collect)"
>
<BaseButton size="small">切换</BaseButton>
</PopConfirm>
</template>
</div>
</div>
<div class="slide-item">
<div class="panel-page-item">
<div class="list-header">
<div class="left">
<div class="dict-name">总词数:{{ store.simple.words.length }}</div>
<Tooltip title="添加">
<IconWrapper>
<Icon icon="fluent:add-12-regular" @click="addSimple"/>
</IconWrapper>
</Tooltip>
</div>
<template v-if="store.currentDict.type !== DictType.simple && store.simple.words.length">
<PopConfirm
:title="`确认切换?`"
@confirm="changeIndex(0,store.simple)"
>
<BaseButton size="small">切换</BaseButton>
</PopConfirm>
</template>
</div>
<template v-if="practiceType === DictType.word">
<WordList
v-if="store.simple.words.length"
v-if="store.collect.words.length"
class="word-list"
:list="store.simple.words">
:list="store.collect.words">
<template v-slot:suffix="{item,index}">
<BaseIcon
class="del"
@click="delSimpleWord(item)"
@click="toggleWordCollect(item)"
title="移除"
icon="solar:trash-bin-minimalistic-linear"/>
</template>
</WordList>
<Empty v-else/>
</div>
</div>
<div class="slide-item">
<div class="panel-page-item" v-if="store.wrong.words.length">
<div class="list-header">
<div class="dict-name">总词数:{{ store.wrong.words.length }}</div>
<template
v-if="store.currentDict.type !== DictType.wrong && store.wrong.words.length">
<PopConfirm
:title="`确认切换?`"
@confirm="changeIndex(0,store.wrong)"
>
<BaseButton size="small">切换</BaseButton>
</PopConfirm>
</template>
</div>
<WordList
class="word-list"
:list="store.wrong.words">
<template v-slot="{item,index}">
</template>
<template v-else>
<ArticleList
v-if="store.collect.articles.length"
:list="store.collect.articles">
<template v-slot:suffix="{item,index}">
<BaseIcon
class="del"
@click="delWrongWord(item)"
@click="toggleArticleCollect(item)"
title="移除"
icon="solar:trash-bin-minimalistic-linear"/>
</template>
</WordList>
</ArticleList>
<Empty v-else/>
</template>
</div>
</div>
<div class="slide-item">
<div class="panel-page-item">
<div class="list-header">
<div class="left">
<div class="dict-name">总词数{{ store.simple.words.length }}</div>
<BaseIcon icon="fluent:add-12-regular" title="添加" @click="addSimple"/>
</div>
<template v-if="store.currentDict.type !== DictType.simple && store.simple.words.length">
<PopConfirm
:title="`确认切换?`"
@confirm="changeIndex( store.simple)"
>
<BaseButton size="small">切换</BaseButton>
</PopConfirm>
</template>
</div>
<WordList
v-if="store.simple.words.length"
class="word-list"
:list="store.simple.words">
<template v-slot:suffix="{item,index}">
<BaseIcon
class="del"
@click="delSimpleWord(item)"
title="移除"
icon="solar:trash-bin-minimalistic-linear"/>
</template>
</WordList>
<Empty v-else/>
</div>
</div>
</div>
<div class="slide-item">
<div class="panel-page-item" v-if="store.wrong.words.length">
<div class="list-header">
<div class="dict-name">总词数{{ store.wrong.words.length }}</div>
<template
v-if="store.currentDict.type !== DictType.wrong && store.wrong.words.length">
<PopConfirm
:title="`确认切换?`"
@confirm="changeIndex( store.wrong)"
>
<BaseButton size="small">切换</BaseButton>
</PopConfirm>
</template>
</div>
<WordList
class="word-list"
:list="store.wrong.words">
<template v-slot="{item,index}">
<BaseIcon
class="del"
@click="delWrongWord(item)"
title="移除"
icon="solar:trash-bin-minimalistic-linear"/>
</template>
</WordList>
</div>
<Empty v-else/>
</div>
</Slide>
</div>
</Transition>
</template>
@@ -228,60 +231,34 @@ function addSimple() {
@import "@/assets/css/variable";
$header-height: 50rem;
.slide-item {
width: var(--panel-width);
height: 100%;
display: flex;
flex-direction: column;
.slide {
width: 100%;
flex: 1;
overflow: hidden;
.slide-list {
width: 400%;
height: 100%;
> header {
padding: 0 var(--space);
height: $header-height;
position: relative;
display: flex;
transition: all .5s;
.slide-item {
width: var(--panel-width);
height: 100%;
display: flex;
flex-direction: column;
> header {
padding: 0 var(--space);
height: $header-height;
position: relative;
display: flex;
align-items: center;
justify-content: flex-end;
gap: 10rem;
font-size: 16rem;
color: black;
}
.content {
flex: 1;
overflow: auto;
padding-bottom: var(--space);
}
footer {
padding-right: var(--space);
margin-bottom: 10rem;
align-items: center;
}
}
align-items: center;
justify-content: flex-end;
gap: 10rem;
font-size: 16rem;
color: black;
}
.step1 {
transform: translate3d(-25%, 0, 0);
.content {
flex: 1;
overflow: auto;
padding-bottom: var(--space);
}
.step2 {
transform: translate3d(-50%, 0, 0);
}
.step3 {
transform: translate3d(-75%, 0, 0);
footer {
padding-right: var(--space);
margin-bottom: 10rem;
align-items: center;
}
}
@@ -326,12 +303,11 @@ $header-height: 50rem;
font-size: 16rem;
&.active {
color: rgb(36, 127, 255);
color: var(--color-main-active);
font-weight: bold;
}
}
}
}
}

View File

@@ -62,21 +62,6 @@ function repeat() {
practiceRef.getCurrentPractice()
}
function next() {
// console.log('next')
if (store.isArticle) {
if (store.currentDict.chapterIndex >= store.currentDict.articles.length - 1) {
store.currentDict.chapterIndex = 0
} else store.currentDict.chapterIndex++
} else {
if (store.currentDict.chapterIndex >= store.currentDict.chapterWords.length - 1) {
store.currentDict.chapterIndex = 0
} else store.currentDict.chapterIndex++
}
repeat()
}
function prev() {
// console.log('next')
if (store.currentDict.chapterIndex === 0) {
@@ -118,12 +103,10 @@ function jumpSpecifiedChapter(val: number) {
}
onMounted(() => {
emitter.on(EventKey.next, next)
emitter.on(EventKey.write, write)
emitter.on(EventKey.repeat, repeat)
emitter.on(EventKey.jumpSpecifiedChapter, jumpSpecifiedChapter)
emitter.on(ShortcutKey.NextChapter, next)
emitter.on(ShortcutKey.PreviousChapter, prev)
emitter.on(ShortcutKey.RepeatChapter, repeat)
emitter.on(ShortcutKey.DictationChapter, write)
@@ -137,12 +120,10 @@ onMounted(() => {
})
onUnmounted(() => {
emitter.off(EventKey.next, next)
emitter.off(EventKey.write, write)
emitter.off(EventKey.repeat, repeat)
emitter.off(EventKey.jumpSpecifiedChapter, jumpSpecifiedChapter)
emitter.off(ShortcutKey.NextChapter, next)
emitter.off(ShortcutKey.PreviousChapter, prev)
emitter.off(ShortcutKey.RepeatChapter, repeat)
emitter.off(ShortcutKey.DictationChapter, write)
@@ -179,7 +160,8 @@ useStartKeyboardEventListener()
flex-direction: column;
justify-content: space-between;
align-items: center;
padding-right: var(--practice-wrapper-padding-right);
//padding-right: var(--practice-wrapper-padding-right);
transform: translateX(var(--practice-wrapper-translateX));
}
</style>

View File

@@ -82,18 +82,6 @@ watch(() => settingStore.dictation, () => {
calcTranslateLocation()
})
onMounted(() => {
emitter.on(EventKey.resetWord, () => {
wrong = input = ''
})
emitter.on(EventKey.onTyping, onTyping)
})
onUnmounted(() => {
emitter.off(EventKey.resetWord,)
emitter.off(EventKey.onTyping, onTyping)
})
function nextSentence() {
// wordData.words = [
// {"name": "pharmacy", "trans": ["药房;配药学,药剂学;制药业;一批备用药品"], "usphone": "'fɑrməsi", "ukphone": "'fɑːməsɪ"},
@@ -218,7 +206,6 @@ function onTyping(e: KeyboardEvent) {
playKeyboardAudio()
}
e.preventDefault()
}
function calcTranslateLocation() {
@@ -247,7 +234,9 @@ function calcTranslateLocation() {
}
function play() {
return playWordAudio('article1')
let currentSection = props.article.sections[sectionIndex]
return playWordAudio(currentSection[sentenceIndex].text)
if (isPlay) {
isPlay = false
return window.speechSynthesis.pause();
@@ -262,55 +251,14 @@ function play() {
window.speechSynthesis.speak(msg);
}
function onKeyDown(e: KeyboardEvent) {
if (!props.active) return
switch (e.key) {
case 'Backspace':
if (wrong) {
wrong = ''
} else {
input = input.slice(0, -1)
}
break
case ShortcutKeyMap.Collect:
break
case ShortcutKeyMap.Remove:
break
case ShortcutKeyMap.Ignore:
nextSentence()
break
case ShortcutKeyMap.Show:
if (settingStore.allowWordTip) {
hoverIndex = {
sectionIndex: sectionIndex,
sentenceIndex: sentenceIndex,
}
}
break
}
// console.log(
// 'sectionIndex', sectionIndex,
// 'sentenceIndex', sentenceIndex,
// 'wordIndex', wordIndex,
// 'stringIndex', stringIndex,
// )
e.preventDefault()
}
function onKeyUp() {
hoverIndex = {
sectionIndex: -1,
sentenceIndex: -1,
function del() {
if (wrong) {
wrong = ''
} else {
input = input.slice(0, -1)
}
}
useOnKeyboardEventListener(onKeyDown, onKeyUp)
// useEventListener('keydown', onKeyDown)
// useEventListener('keyup', onKeyUp)
function playWord(word: ArticleWord) {
playWordAudio(word.name)
}
@@ -366,6 +314,30 @@ const {
toggleArticleCollect
} = useArticleOptions()
function showSentence(i1: number = sectionIndex, i2: number = sentenceIndex) {
hoverIndex = {sectionIndex: i1, sentenceIndex: i2}
}
function hideSentence() {
hoverIndex = {sectionIndex: -1, sentenceIndex: -1}
}
onMounted(() => {
emitter.on(EventKey.resetWord, () => {
wrong = input = ''
})
emitter.on(EventKey.onTyping, onTyping)
})
onUnmounted(() => {
emitter.off(EventKey.resetWord,)
emitter.off(EventKey.onTyping, onTyping)
})
defineExpose({showSentence, play, del,hideSentence,nextSentence})
</script>
<template>
@@ -408,8 +380,8 @@ const {
sectionIndex === indexI && sentenceIndex === indexJ && settingStore.dictation
?'dictation':''
]"
@mouseenter="settingStore.allowWordTip && (hoverIndex = {sectionIndex : indexI,sentenceIndex :indexJ})"
@mouseleave="hoverIndex = {sectionIndex : -1,sentenceIndex :-1}"
@mouseenter="settingStore.allowWordTip && showSentence(indexI,indexJ)"
@mouseleave="hideSentence"
@click="playWordAudio(sentence.text)"
v-for="(sentence,indexJ) in section">
<span

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import {$ref} from "vue/macros";
import {$computed, $ref} from "vue/macros";
import TypingArticle from "./TypingArticle.vue";
import {
Article,
@@ -14,7 +14,7 @@ import {
import {cloneDeep} from "lodash-es";
import TypingWord from "@/pages/practice/practice-word/TypingWord.vue";
import Panel from "../Panel.vue";
import {onMounted, watch} from "vue";
import {onMounted, onUnmounted, watch} from "vue";
import {renewSectionTexts, renewSectionTranslates} from "@/hooks/translate.ts";
import {MessageBox} from "@/utils/MessageBox.tsx";
import {useBaseStore} from "@/stores/base.ts";
@@ -29,6 +29,7 @@ import {useSettingStore} from "@/stores/setting.ts";
import BaseIcon from "@/components/BaseIcon.vue";
import {syncMyDictList, useArticleOptions} from "@/hooks/dict.ts";
import ArticleList from "@/components/list/ArticleList.vue";
import {useOnKeyboardEventListener} from "@/hooks/event.ts";
const store = useBaseStore()
const practiceStore = usePracticeStore()
@@ -40,6 +41,7 @@ let wordData = $ref({
index: -1
})
let articleData = $ref({
articles: [],
article: cloneDeep(DefaultArticle),
sectionIndex: 0,
sentenceIndex: 0,
@@ -47,20 +49,30 @@ let articleData = $ref({
stringIndex: 0,
})
let showEditArticle = $ref(false)
let typingArticleRef = $ref<any>()
let editArticle = $ref<Article>(cloneDeep(DefaultArticle))
let articleIsActive = $computed(() => tabIndex === 0)
watch([
// () => store.load,
() => store.currentDict.articles,
], n => {
function next() {
if (!articleIsActive) return
if (store.currentDict.chapterIndex >= articleData.articles.length - 1) {
store.currentDict.chapterIndex = 0
} else store.currentDict.chapterIndex++
emitter.emit(EventKey.resetWord)
getCurrentPractice()
})
}
onMounted(getCurrentPractice)
function init() {
if (!store.currentDict.articles.length) return
articleData.articles = cloneDeep(store.currentDict.articles)
getCurrentPractice()
}
function setArticle(val: Article) {
store.currentDict.articles[store.currentDict.chapterIndex] = cloneDeep(val)
articleData.article = cloneDeep(val)
let tempVal = cloneDeep(val)
articleData.articles[store.currentDict.chapterIndex] = tempVal
articleData.article = tempVal
practiceStore.inputWordNumber = 0
practiceStore.wrongWordNumber = 0
practiceStore.repeatNumber = 0
@@ -81,11 +93,10 @@ function setArticle(val: Article) {
function getCurrentPractice() {
// console.log('store.currentDict',store.currentDict)
// return
if (!store.currentDict.articles.length) return
tabIndex = 0
articleData.article = cloneDeep(DefaultArticle)
let currentArticle = store.currentDict.articles[store.currentDict.chapterIndex]
let currentArticle = articleData.articles [store.currentDict.chapterIndex]
let tempArticle = {...DefaultArticle, ...currentArticle}
// console.log('article', tempArticle)
if (tempArticle.sections.length) {
@@ -153,11 +164,15 @@ function getCurrentPractice() {
function saveArticle(val: Article) {
console.log('saveArticle', val)
showEditArticle = false
// articleData.article = cloneDeep(store.currentDict.articles[store.currentDict.chapterIndex])
let rIndex = store.currentDict.articles.findIndex(v => v.id === val.id)
if (rIndex > -1) {
store.currentDict.articles[rIndex] = cloneDeep(val)
}
setArticle(val)
}
function edit(val: Article) {
function edit(val: Article = articleData.article) {
if (!articleIsActive) return
// tabIndex = 1
// wordData.words = [
// {
@@ -213,8 +228,8 @@ function nextWord(word: ArticleWord) {
}
}
function changePracticeArticle(val: ArticleItem) {
let rIndex = store.currentDict.articles.findIndex(v => v.id === val.item.id)
function handleChangeChapterIndex(val: ArticleItem) {
let rIndex = articleData.articles.findIndex(v => v.id === val.item.id)
if (rIndex > -1) {
store.currentDict.chapterIndex = rIndex
getCurrentPractice()
@@ -233,6 +248,71 @@ function sort(list: Word[]) {
wordData.index = 0
}
function play() {
if (!articleIsActive) return
typingArticleRef?.play()
}
function show() {
if (!articleIsActive) return
typingArticleRef?.showSentence()
}
function onKeyUp(e: KeyboardEvent) {
typingArticleRef.hideSentence()
}
async function onKeyDown(e: KeyboardEvent) {
// console.log('e', e)
switch (e.key) {
case 'Backspace':
typingArticleRef.del()
break
}
}
useOnKeyboardEventListener(onKeyDown, onKeyUp)
function skip() {
if (!articleIsActive) return
typingArticleRef?.nextSentence()
}
function collect(e: KeyboardEvent) {
if (!articleIsActive) return
toggleArticleCollect(articleData.article)
}
//包装一遍因为快捷建的默认参数是Event
function shortcutKeyEdit() {
edit()
}
onMounted(() => {
init()
emitter.on(EventKey.changeDict, init)
emitter.on(EventKey.next, next)
emitter.on(ShortcutKey.NextChapter, next)
emitter.on(ShortcutKey.PlayWordPronunciation, play)
emitter.on(ShortcutKey.ShowWord, show)
emitter.on(ShortcutKey.Next, skip)
emitter.on(ShortcutKey.ToggleCollect, collect)
emitter.on(ShortcutKey.EditArticle, shortcutKeyEdit)
})
onUnmounted(() => {
emitter.off(EventKey.changeDict, init)
emitter.off(EventKey.next, next)
emitter.off(ShortcutKey.NextChapter, next)
emitter.off(ShortcutKey.PlayWordPronunciation, play)
emitter.off(ShortcutKey.ShowWord, show)
emitter.off(ShortcutKey.Next, skip)
emitter.off(ShortcutKey.ToggleCollect, collect)
emitter.off(ShortcutKey.EditArticle, shortcutKeyEdit)
})
defineExpose({getCurrentPractice})
</script>
@@ -243,10 +323,11 @@ defineExpose({getCurrentPractice})
<div class="swiper-list" :class="`step${tabIndex}`">
<div class="swiper-item">
<TypingArticle
ref="typingArticleRef"
:active="tabIndex === 0"
@edit="edit"
@wrong="wrong"
@over="over"
@over="skip"
@nextWord="nextWord"
:article="articleData.article"
/>
@@ -264,57 +345,59 @@ defineExpose({getCurrentPractice})
</div>
</div>
<div class="panel-wrapper">
<Panel v-if="tabIndex === 0">
<template v-slot="{active}">
<div class="panel-page-item">
<div class="list-header">
<div class="left">
<BaseIcon title="切换词典"
@click="emitter.emit(EventKey.openDictModal,'list')"
icon="carbon:change-catalog"/>
<div class="title">
{{ store.dictTitle }}
<Teleport to="body">
<div class="panel-wrapper">
<Panel v-if="tabIndex === 0">
<template v-slot="{active}">
<div class="panel-page-item">
<div class="list-header">
<div class="left">
<BaseIcon title="切换词典"
@click="emitter.emit(EventKey.openDictModal,'list')"
icon="carbon:change-catalog"/>
<div class="title">
{{ store.currentDict.name }}
</div>
<Tooltip
:title="`下一章(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.NextChapter]})`"
v-if="store.currentDict.chapterIndex < articleData.articles .length - 1">
<IconWrapper>
<Icon @click="emitter.emit(EventKey.next)" icon="octicon:arrow-right-24"/>
</IconWrapper>
</Tooltip>
</div>
<div class="right">
{{ articleData.articles.length }}篇文章
</div>
<Tooltip
:title="`下一章(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.NextChapter]})`"
v-if="store.currentDict.chapterIndex < store.currentDict.articles.length - 1">
<IconWrapper>
<Icon @click="emitter.emit(EventKey.next)" icon="octicon:arrow-right-24"/>
</IconWrapper>
</Tooltip>
</div>
<div class="right">
{{ store.currentDict.articles.length }}篇文章
</div>
</div>
<ArticleList
:isActive="active"
:static="false"
:show-border="true"
:show-translate="settingStore.translate"
@title="e => emitter.emit(EventKey.openArticleContentModal,e.item)"
@click="changePracticeArticle"
:active-id="articleData.article.id"
:list="store.currentDict.articles">
<template v-slot:suffix="{item,index}">
<BaseIcon
v-if="!isArticleCollect(item)"
class="collect"
@click="toggleArticleCollect(item)"
title="收藏" icon="ph:star"/>
<BaseIcon
v-else
class="fill"
@click="toggleArticleCollect(item)"
title="取消收藏" icon="ph:star-fill"/>
</template>
</ArticleList>
</div>
</template>
</Panel>
</div>
<ArticleList
:isActive="active"
:static="false"
:show-border="true"
:show-translate="settingStore.translate"
@title="e => emitter.emit(EventKey.openArticleContentModal,e.item)"
@click="handleChangeChapterIndex"
:active-id="articleData.article.id"
:list="articleData.articles ">
<template v-slot:suffix="{item,index}">
<BaseIcon
v-if="!isArticleCollect(item)"
class="collect"
@click="toggleArticleCollect(item)"
title="收藏" icon="ph:star"/>
<BaseIcon
v-else
class="fill"
@click="toggleArticleCollect(item)"
title="取消收藏" icon="ph:star-fill"/>
</template>
</ArticleList>
</div>
</template>
</Panel>
</div>
</Teleport>
<EditSingleArticleModal
v-model="showEditArticle"
@@ -327,8 +410,6 @@ defineExpose({getCurrentPractice})
<style scoped lang="scss">
@import "@/assets/css/style";
$article-width: 50vw;
.swiper-wrapper {
height: 100%;
overflow: hidden;
@@ -353,7 +434,7 @@ $article-width: 50vw;
.practice-article {
flex: 1;
overflow: hidden;
width: $article-width;
width: var(--article-width);
}
.typing-word-wrapper {
@@ -365,7 +446,7 @@ $article-width: 50vw;
left: 0;
top: 10rem;
z-index: 1;
margin-left: calc(50% + ($article-width / 2) + var(--space));
margin-left: var(--article-panel-margin-left);
height: calc(100% - 20rem);
}

View File

@@ -140,17 +140,13 @@ function next(isTyping: boolean = true) {
data.index++
isTyping && practiceStore.inputWordNumber++
console.log('这个词完了')
if ([DictType.customWord, DictType.word].includes(store.currentDict.type)
if ([DictType.word].includes(store.currentDict.type)
&& store.skipWordNames.includes(word.name.toLowerCase())) {
next()
}
}
}
function onKeyUp(e: KeyboardEvent) {
typingRef.hideWord()
}
function wordWrong() {
if (!store.wrong.originWords.find((v: Word) => v.name.toLowerCase() === word.name.toLowerCase())) {
store.wrong.originWords.push(word)
@@ -161,6 +157,10 @@ function wordWrong() {
}
}
function onKeyUp(e: KeyboardEvent) {
typingRef.hideWord()
}
async function onKeyDown(e: KeyboardEvent) {
// console.log('e', e)
switch (e.key) {
@@ -433,14 +433,11 @@ onUnmounted(() => {
}
}
$article-width: 50vw;
.word-panel-wrapper {
position: fixed;
left: 0;
top: 10rem;
z-index: 1;
//margin-left: calc(50% + (var(--toolbar-width) / 2) + var(--space));
margin-left: var(--panel-margin-left);
height: calc(100% - 20rem);
}

View File

@@ -6,7 +6,7 @@ import {chunk, cloneDeep} from "lodash-es";
import {useBaseStore} from "@/stores/base.ts";
import {onMounted, onUnmounted, watch} from "vue";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {Word} from "@/types.ts";
import {ShortcutKey, Word} from "@/types.ts";
import {emitter, EventKey} from "@/utils/eventBus.ts";
import {useSettingStore} from "@/stores/setting.ts";
import {syncMyDictList} from "@/hooks/dict.ts";
@@ -20,12 +20,6 @@ let wordData = $ref({
index: -1
})
watch([
() => store.currentDict.words,
], n => {
getCurrentPractice()
})
function getCurrentPractice() {
if (store.chapter.length) {
wordData.words = store.chapter
@@ -37,6 +31,9 @@ function getCurrentPractice() {
if (res) w = Object.assign(w, res)
}
})
wordData.words = cloneDeep(store.chapter)
emitter.emit(EventKey.resetWord)
}
}
@@ -46,7 +43,26 @@ function sort(list: Word[]) {
syncMyDictList(store.currentDict)
}
onMounted(getCurrentPractice)
function next() {
if (store.currentDict.chapterIndex >= store.currentDict.chapterWords.length - 1) {
store.currentDict.chapterIndex = 0
} else store.currentDict.chapterIndex++
getCurrentPractice()
}
onMounted(() => {
getCurrentPractice()
emitter.on(EventKey.changeDict, getCurrentPractice)
emitter.on(EventKey.next, next)
emitter.on(ShortcutKey.NextChapter, next)
})
onUnmounted(() => {
emitter.off(EventKey.changeDict, getCurrentPractice)
emitter.off(EventKey.next, next)
emitter.off(ShortcutKey.NextChapter, next)
})
defineExpose({getCurrentPractice})

View File

@@ -88,6 +88,19 @@ export const useBaseStore = defineStore('base', {
type: DictType.wrong,
category: '自带字典'
},
{
...cloneDeep(DefaultDict),
id: 'cet4',
name: 'CET-4',
description: '大学英语四级词库',
category: '中国考试',
tags: ['大学英语'],
url: 'CET4_T.json',
length: 2607,
translateLanguage: 'common',
language: 'en',
type: DictType.word
},
{
...cloneDeep(DefaultDict),
id: 'article_nce2',
@@ -100,6 +113,7 @@ export const useBaseStore = defineStore('base', {
language: 'en',
type: DictType.article,
resourceId: 'article_nce2',
length: 10
},
{
...cloneDeep(DefaultDict),
@@ -113,10 +127,11 @@ export const useBaseStore = defineStore('base', {
language: 'en',
type: DictType.word,
resourceId: 'nce-new-2',
length: 862
},
],
current: {
index: 4,
index: 3,
// dictType: DictType.article,
// index: 0,
practiceType: DictType.word,
@@ -163,20 +178,17 @@ export const useBaseStore = defineStore('base', {
chapter(state: BaseState): Word[] {
return this.currentDict.chapterWords[this.currentDict.chapterIndex] ?? []
},
dictTitle(state: BaseState) {
let title = this.currentDict.name
return title + this.chapterName
},
chapterName(state: BaseState) {
let title = ''
switch (this.currentDict.type) {
case DictType.collect:
if (state.current.practiceType === DictType.article || state.current.practiceType === DictType.customArticle) {
if (state.current.practiceType === DictType.article) {
return `${this.currentDict.chapterIndex + 1}`
}
return ''
case DictType.wrong:
case DictType.simple:
return this.currentDict.name
case DictType.word:
case DictType.customWord:
return `${this.currentDict.chapterIndex + 1}`
}
return title
@@ -213,6 +225,7 @@ export const useBaseStore = defineStore('base', {
} catch (e) {
console.error('读取本地dict数据失败', e)
}
const runtimeStore = useRuntimeStore()
if (this.current.index < 3) {
@@ -227,7 +240,6 @@ export const useBaseStore = defineStore('base', {
s.id = nanoid(6)
})
if (this.currentDict.translateLanguage === 'common') {
const runtimeStore = useRuntimeStore()
let r2 = await fetch('./translate/en2zh_CN-min.json')
// fetch('http://sc.ttentau.top/en2zh_CN-min.json').then(r2 => {
let list: Word[] = await r2.json()
@@ -252,6 +264,19 @@ export const useBaseStore = defineStore('base', {
}
}
}
//TODO 先这样,默认加载
if (!runtimeStore.translateWordList.length) {
setTimeout(async () => {
let r2 = await fetch('./translate/en2zh_CN-min.json')
// fetch('http://sc.ttentau.top/en2zh_CN-min.json').then(r2 => {
let list: Word[] = await r2.json()
if (list && list.length) {
runtimeStore.translateWordList = list
}
})
}
emitter.emit(EventKey.changeDict)
resolve(true)
})
},
@@ -261,11 +286,13 @@ export const useBaseStore = defineStore('base', {
this.currentDict.statistics.push(statistics)
}
},
async changeDict(dict: Dict, chapterIndex: number = dict.chapterIndex, wordIndex: number = dict.wordIndex, practiceType: DictType) {
async changeDict(dict: Dict, practiceType?: DictType, chapterIndex?: number, wordIndex?: number) {
//TODO 保存统计
// this.saveStatistics()
console.log('changeDict', cloneDeep(dict), chapterIndex, wordIndex)
this.current.practiceType = practiceType
if (chapterIndex === undefined) chapterIndex = dict.chapterIndex
if (wordIndex === undefined) wordIndex = dict.wordIndex
if (practiceType === undefined) this.current.practiceType = practiceType
if ([DictType.collect,
DictType.simple,
DictType.wrong].includes(dict.type)) {
@@ -296,7 +323,6 @@ export const useBaseStore = defineStore('base', {
this.current.index = this.myDictList.length - 1
}
emitter.emit(EventKey.resetWord)
emitter.emit(EventKey.changeDict)
}
},

View File

@@ -54,7 +54,7 @@ export const useSettingStore = defineStore('setting', {
wordSoundType: 'us',
keyboardSound: true,
keyboardSoundVolume: 100,
keyboardSoundFile: '老式机械',
keyboardSoundFile: '机械键盘2',
translateSound: true,
translateSoundVolume: 100,
effectSound: true,

View File

@@ -46,9 +46,7 @@ export enum DictType {
simple = 'simple',
wrong = 'wrong',
word = 'word',
customWord = 'customWord',
article = 'article',
customArticle = 'customArticle'
}
export const DefaultArticleWord: ArticleWord = {
@@ -235,7 +233,7 @@ export const DefaultDict: Dict = {
category: '',
tags: [],
translateLanguage: 'common',
type: DictType.customWord,
type: DictType.word,
language: 'en',
}

View File

@@ -1,7 +1,7 @@
export const SoundFileOptions = [
{value: '快速打字的机械键盘声音', label: '快速打字的机械键盘声音'},
{value: '键盘快速打字的声音', label: '键盘快速打字的声音'},
{value: '电话打字的声音', label: '电话打字的声音'},
{value: '老式机械', label: '老式机械'},
{value: '机械', label: '机械'},
{value: '机械键盘', label: '机械键盘'},
{value: '机械键盘1', label: '机械键盘1'},
{value: '机械键盘2', label: '机械键盘2'},
{value: '老式机械键盘', label: '老式机械键盘'},
{value: '笔记本键盘', label: '笔记本键盘'},
]