Merge branch 'dev'
This commit is contained in:
@@ -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"
|
||||
|
||||
27
src/App.vue
27
src/App.vue
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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]"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ $w2: calc($w / 2);
|
||||
.circle-detail {
|
||||
transform-origin: $w2 $w2;
|
||||
transform: rotate(-90deg);
|
||||
stroke: $main;
|
||||
stroke: var(--color-main-active);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -65,7 +65,7 @@ onUnmounted(() => {
|
||||
display: flex;
|
||||
gap: var(--space);
|
||||
padding: var(--space);
|
||||
|
||||
color: var(--color-font-1);
|
||||
|
||||
.article-content {
|
||||
flex: 1;
|
||||
|
||||
@@ -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>
|
||||
<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>
|
||||
<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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -273,7 +273,6 @@ export function getSplitTranslateText(article: string) {
|
||||
export function isArticle(type: DictType): boolean {
|
||||
return [
|
||||
DictType.article,
|
||||
DictType.customArticle
|
||||
].includes(type)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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`,
|
||||
|
||||
@@ -80,6 +80,7 @@ async function getDictDetail(val: {
|
||||
runtimeStore.editDict.length = runtimeStore.editDict.articles.length
|
||||
}
|
||||
}
|
||||
|
||||
loading = false
|
||||
}
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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})
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
},
|
||||
|
||||
@@ -54,7 +54,7 @@ export const useSettingStore = defineStore('setting', {
|
||||
wordSoundType: 'us',
|
||||
keyboardSound: true,
|
||||
keyboardSoundVolume: 100,
|
||||
keyboardSoundFile: '老式机械',
|
||||
keyboardSoundFile: '机械键盘2',
|
||||
translateSound: true,
|
||||
translateSoundVolume: 100,
|
||||
effectSound: true,
|
||||
|
||||
@@ -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',
|
||||
}
|
||||
|
||||
|
||||
@@ -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: '笔记本键盘'},
|
||||
]
|
||||
Reference in New Issue
Block a user