This commit is contained in:
zyronon
2024-05-19 17:53:22 +08:00
parent 0c1c5d33b0
commit 3c0a0df3bf
26 changed files with 421 additions and 614 deletions

View File

@@ -7,7 +7,7 @@
:root {
--color-background: #E6E8EB;
//--color-main-bg: #E6E8EB;
--color-main-bg: rgb(238,240,244);
--color-main-bg: rgb(238, 240, 244);
--color-second-bg: rgb(240, 242, 244);
--color-third-bg: rgb(213, 215, 217);
@@ -35,7 +35,7 @@
--toolbar-width: 45rem;
--toolbar-height: 3.2rem;
--panel-width: 24rem;
--space: 1.6rem;
--space: 1.2rem;
--radius: .5rem;
--shadow: rgba(0, 0, 0, 0.08) 0px 4px 12px;
--panel-margin-left: calc(50% - var(--practice-wrapper-translateX) / 2 + var(--toolbar-width) / 2 + 2rem);
@@ -47,6 +47,8 @@
--color-textarea-bg: white;
--aside-width: 12rem;
--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;
}
@@ -483,3 +485,7 @@ footer {
position: relative;
}
}
.container2 {
@apply w-5/10 pt-5;
}

View File

@@ -21,7 +21,7 @@ defineEmits(['click'])
</script>
<template>
<Tooltip :disabled="!keyboard" :title="`快捷键: ${keyboard}`">
<Tooltip :disabled="!keyboard" :title="`${keyboard}`">
<div class="base-button"
v-bind="$attrs"
@click="e => (!disabled && !loading) && $emit('click',e)"

View File

@@ -210,7 +210,7 @@ watch(() => props.word, () => {
<span class="letter" v-else>{{ displayWord }}</span>
</div>
<Tooltip
:title="`发音(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.PlayWordPronunciation]})`"
:title="`发音(${settingStore.shortcutKeyMap[ShortcutKey.PlayWordPronunciation]})`"
>
<VolumeIcon ref="volumeIconRef" :simple="true" :cb="() => playWordAudio(word.word)"/>
</Tooltip>

View File

@@ -198,9 +198,6 @@ $header-height: 4rem;
opacity: 0;
transform: scale(0);
}
50% {
transform: scale(1.15);
}
100% {
opacity: 1;
transform: scale(1);

View File

@@ -1,8 +1,8 @@
<script setup lang="ts">
import {useBaseStore} from "@/stores/base.ts"
import {onMounted} from "vue"
import {DefaultDict, Dict, DictResource, DictType, Sort, Word} from "@/types.ts"
import {chunk, cloneDeep, reverse, shuffle} from "lodash-es";
import {DefaultDict, Dict, DictResource, DictType} from "@/types.ts"
import {chunk, cloneDeep} from "lodash-es";
import {$computed, $ref} from "vue/macros";
import BaseButton from "@/components/BaseButton.vue";
import {Icon} from '@iconify/vue';
@@ -12,16 +12,11 @@ import {isArticle} from "@/hooks/article.ts";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {useSettingStore} from "@/stores/setting.ts";
import {emitter, EventKey} from "@/utils/eventBus.ts";
import Slide from "@/pages/pc/components/Slide.vue";
import Empty from "@/components/Empty.vue";
import BaseIcon from "@/components/BaseIcon.vue";
import Dialog from "@/pages/pc/components/dialog/Dialog.vue";
import EditBatchArticleModal from "@/pages/pc/components/article/EditBatchArticleModal.vue";
import {nanoid} from "nanoid";
import DictListPanel from "@/pages/pc/components/DictListPanel.vue";
import {useRouter} from "vue-router";
import ArticleList from "@/pages/pc/components/list/ArticleList.vue";
import BaseList from "@/pages/pc/components/list/BaseList.vue";
import {MessageBox} from "@/utils/MessageBox.tsx";
import {getDictFile} from "@/utils";
@@ -30,25 +25,14 @@ const settingStore = useSettingStore()
const runtimeStore = useRuntimeStore()
let router = useRouter()
let step = $ref(1)
let loading = $ref(false)
let show = $ref(false)
let chapterList2 = $ref([])
let chapterWordNumber = $ref(0)
let toggleLoading = $ref(false)
const activeId = $computed(() => {
if (dictIsArticle) {
return runtimeStore.editDict.articles?.[runtimeStore.editDict.chapterIndex].id ?? ''
}
return ''
})
async function selectDict(val: { dict: DictResource | Dict, index: number }) {
let item = val.dict
// console.log('item', item)
step = 1
loading = true
let find: Dict = store.myDictList.find((v: Dict) => v.id === item.id)
if (find) {
runtimeStore.editDict = cloneDeep(find)
@@ -74,7 +58,6 @@ async function selectDict(val: { dict: DictResource | Dict, index: number }) {
s.id = nanoid(6)
})
runtimeStore.editDict.originWords = cloneDeep(v)
changeSort(runtimeStore.editDict.sort, false)
} else {
runtimeStore.editDict.length = runtimeStore.editDict.words.length
}
@@ -93,8 +76,6 @@ async function selectDict(val: { dict: DictResource | Dict, index: number }) {
}
}
chapterWordNumber = runtimeStore.editDict.chapterWordNumber
chapterList2 = runtimeStore.editDict.chapterWords.map((v, i) => ({id: i}))
loading = false
}
@@ -128,45 +109,14 @@ function resetChapterList(v: number) {
const temp = () => {
runtimeStore.editDict.chapterWordNumber = v
runtimeStore.editDict.chapterWords = chunk(runtimeStore.editDict.words, runtimeStore.editDict.chapterWordNumber)
chapterList2 = runtimeStore.editDict.chapterWords.map((v, i) => ({id: i}))
}
if (runtimeStore.editDict.isCustom) {
MessageBox.confirm(
'检测到您已对这本词典自定义修改,修改“每章单词数”将会导致所有章节被重新分配,原有章节内容将被清除且不可恢复,是否继续?',
'提示',
() => temp(),
() => {
chapterWordNumber = runtimeStore.editDict.chapterWordNumber
}
)
} else {
temp()
}
}
function changeSort(v: Sort, notice: boolean = true) {
const temp = () => {
runtimeStore.editDict.sort = v
if (v === Sort.normal) {
runtimeStore.editDict.words = cloneDeep(runtimeStore.editDict.originWords)
} else if (v === Sort.random) {
runtimeStore.editDict.words = shuffle(cloneDeep(runtimeStore.editDict.originWords))
} else {
runtimeStore.editDict.words = reverse(cloneDeep(runtimeStore.editDict.originWords))
}
resetChapterList(runtimeStore.editDict.chapterWordNumber)
notice && ElMessage.success('已重新排序')
}
if (runtimeStore.editDict.isCustom) {
MessageBox.confirm(
'检测到您已对这本词典自定义修改,修改“单词排序”将会导致所有章节被重新分配,原有章节内容将被清除且不可恢复,是否继续?',
'提示',
() => temp()
)
} else {
temp()
}
}
function option(type: string) {
show = false
@@ -180,32 +130,10 @@ onMounted(() => {
if (type === "detail") {
selectDict({dict: store.currentDict, index: 0})
}
if (type === "list") {
// currentLanguage = 'en'
step = 0
}
if (type === "my") {
// currentLanguage = 'my'
step = 0
}
show = true
})
})
function showWordListModal(val: { item: Word, index: number }) {
emitter.emit(EventKey.openWordListModal, {
title: `${val.index + 1}`,
translateLanguage: runtimeStore.editDict.translateLanguage,
list: runtimeStore.editDict.chapterWords[val.index]
})
}
function handleChangeArticleChapterIndex(val: any) {
let rIndex = runtimeStore.editDict.articles.findIndex(v => v.id === val.item.id)
if (rIndex > -1) {
runtimeStore.editDict.chapterIndex = rIndex
}
}
</script>
@@ -215,205 +143,59 @@ function handleChangeArticleChapterIndex(val: any) {
v-model="show"
:show-close="false">
<div id="DictDialog">
<Slide :slide-count="2" :step="step">
<DictListPanel
@add="option('addDict')"
@select-dict="selectDict"
/>
<div class="dict-detail-page">
<header>
<div class="left" @click.stop="step = 0">
<Icon icon="octicon:arrow-left-24" class="go" width="20"/>
<div class="title">
词典详情
</div>
</div>
<Icon @click="close"
class="hvr-grow pointer"
width="20" color="#929596"
icon="ion:close-outline"/>
</header>
<div class="detail">
<div class="page-content">
<div class="left-column">
<BaseIcon
v-if="![DictType.collect,DictType.wrong,DictType.simple].includes(runtimeStore.editDict.type)"
class="edit-icon"
title="编辑词典"
icon="tabler:edit"
@click='option("editDict")'
/>
<div class="name">{{ runtimeStore.editDict.name }}</div>
<div class="desc">{{ runtimeStore.editDict.description }}</div>
<div class="text flex align-center">
<div v-if="dictIsArticle">总文章{{ runtimeStore.editDict.articles.length }}
</div>
<div v-else>总词汇
<span class="count" @click="showAllWordModal">{{
runtimeStore.editDict.originWords.length
}}</span>
</div>
<BaseIcon icon="mi:add"
@click='option("addWordOrArticle")'
:title="`添加${dictIsArticle?'文章':'单词'}`"
/>
</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>
<div class="setting">
<template v-if="!dictIsArticle">
<div class="row">
<div class="label">每章单词数</div>
<el-slider
class="my-slider"
:min="10"
:step="10"
:max="runtimeStore.editDict.words.length < 10 ? 10 : runtimeStore.editDict.words.length"
show-input
:show-input-controls="false"
size="small"
v-model="chapterWordNumber"
@change="resetChapterList"
/>
</div>
<div class="notice">
<span class="text">最小:10</span>
<span class="text">最大:{{ runtimeStore.editDict.words.length }}</span>
</div>
<div class="row">
<div class="label">单词顺序</div>
<div class="option">
<el-radio-group :model-value="runtimeStore.editDict.sort"
@change="changeSort"
>
<el-radio :label="Sort.normal" size="large">默认</el-radio>
<el-radio :label="Sort.random" size="large">随机</el-radio>
<el-radio :label="Sort.reverse" size="large">反转</el-radio>
</el-radio-group>
</div>
</div>
</template>
<div class="row">
<div class="label">学习模式</div>
<div class="option">
<el-radio-group v-model="settingStore.dictation">
<el-radio :label="false" size="large">再认</el-radio>
<el-radio :label="true" size="large">拼写</el-radio>
</el-radio-group>
</div>
</div>
<div class="row">
<div class="label">{{ dictIsArticle ? '句子' : '单词' }}发音</div>
<div class="option">
<el-radio-group v-model="settingStore.wordSoundType">
<el-radio label="us" size="large">美音</el-radio>
<el-radio label="uk" size="large">英音</el-radio>
</el-radio-group>
</div>
</div>
<div class="row">
<div class="label">{{ dictIsArticle ? '句子' : '单词' }}自动发音</div>
<div class="option">
<el-switch v-model="settingStore.wordSound"
inline-prompt
active-text=""
inactive-text=""
/>
</div>
</div>
<div class="row">
<div class="label">是否显示翻译</div>
<div class="option">
<el-switch v-model="settingStore.translate"
inline-prompt
active-text=""
inactive-text=""
/>
</div>
</div>
<div class="row">
<div class="label">忽略大小写</div>
<div class="option">
<el-switch v-model="settingStore.ignoreCase"
inline-prompt
active-text=""
inactive-text=""
/>
</div>
</div>
</div>
</div>
<div class="right-column">
<div class="common-title">
<span>{{ dictIsArticle ? '文章' : '章节' }}列表</span>
<BaseIcon
icon="fluent:notepad-edit-20-regular"
@click='option("detail")'
style="position: absolute;right: 20rem;"
:title="`管理${dictIsArticle?'文章':'章节'}`"
/>
</div>
<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>
<header>
<div class="text-2xl">
{{ runtimeStore.editDict.name }}
</div>
</Slide>
<Icon @click="close"
class="hvr-grow pointer"
width="20" color="#929596"
icon="ion:close-outline"/>
</header>
<div class="detail">
<div class="desc">{{ runtimeStore.editDict.description }}</div>
<div class="text flex align-center">
<div v-if="dictIsArticle">总文章{{ runtimeStore.editDict.articles.length }}
</div>
<div v-else>总词汇
<span class="count" @click="showAllWordModal">{{
runtimeStore.editDict.originWords.length
}}</span>
</div>
<BaseIcon icon="mi:add"
@click='option("addWordOrArticle")'
: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>
<div class="row">
<div class="label">每日目标</div>
<el-slider
class="my-slider"
:min="10"
:step="10"
:max="runtimeStore.editDict.words.length < 10 ? 10 : 500"
size="small"
v-model="chapterWordNumber"
@change="resetChapterList"
/>
</div>
<div class="notice">
<span class="text">最小:10</span>
<span class="text">最大:{{ runtimeStore.editDict.words.length < 10 ? 10 : 500 }}</span>
</div>
</div>
<div class="footer">
<BaseButton @click="close">关闭</BaseButton>
<BaseButton :loading="toggleLoading" @click="changeDict">切换</BaseButton>
</div>
</div>
</Dialog>
<WordListDialog/>
@@ -432,13 +214,7 @@ $header-height: 4rem;
//transform: translate(-50%, -50%);
background: var(--color-second-bg);
z-index: 99999;
width: 60rem;
height: 75vh;
}
.dict-detail-page {
width: 50%;
height: 100%;
width: 30rem;
box-sizing: border-box;
display: flex;
flex-direction: column;
@@ -465,109 +241,48 @@ $header-height: 4rem;
padding-left: var(--space);
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
flex-direction: column;
background: var(--color-second-bg);
color: var(--color-font-1);
gap: .2rem;
position: relative;
font-size: .9rem;
padding-right: var(--space);
.page-content {
flex: 1;
overflow: hidden;
display: flex;
position: relative;
.column {
background: var(--color-second-bg);
color: var(--color-font-1);
display: flex;
flex-direction: column;
}
.left-column {
flex: 5;
gap: .6rem;
position: relative;
font-size: .9rem;
padding-right: var(--space);
@extend .column;
.name {
font-size: 1.6rem;
width: 95%;
}
.desc {
font-size: 1rem;
margin-bottom: 1.2rem;
}
.count {
cursor: pointer;
border-bottom: 2px solid var(--color-item-active);
}
:deep(.edit-icon) {
position: absolute;
top: 0;
right: 0;
}
}
.center-column {
overflow: auto;
flex: 7;
@extend .column;
.setting {
.row {
display: flex;
align-items: center;
justify-content: space-between;
height: 2rem;
word-break: keep-all;
gap: .6rem;
.el-radio {
margin-right: .6rem;
}
}
.my-slider {
:deep(.el-slider__input) {
width: 55px;
}
}
.notice {
display: flex;
justify-content: space-between;
transform: translate3d(0, -.3rem, 0);
padding-left: 6rem;
font-size: .8rem;
}
}
}
.right-column {
flex: 7;
@extend .column;
.list-content {
flex: 1;
overflow: hidden;
display: flex;
}
}
.name {
font-size: 1.6rem;
width: 95%;
}
.footer {
box-sizing: content-box;
display: flex;
align-items: flex-end;
justify-content: flex-end;
gap: var(--space);
padding-right: var(--space);
margin: var(--space) 0;
.desc {
font-size: 1rem;
margin-bottom: 1.2rem;
}
.count {
cursor: pointer;
border-bottom: 2px solid var(--color-item-active);
}
:deep(.edit-icon) {
position: absolute;
top: 0;
right: 0;
}
}
.footer {
box-sizing: content-box;
display: flex;
align-items: flex-end;
justify-content: flex-end;
gap: var(--space);
padding-right: var(--space);
margin: var(--space) 0;
}
}
</style>

View File

@@ -28,13 +28,16 @@ watch(() => props.groupByTag, () => {
<template>
<div class="dict-group">
<div class="category">{{ category }}</div>
<div class="tags">
<div class="tag" :class="i === currentTag &&'active'"
@click="currentTag = i"
v-for="i in Object.keys(groupByTag)">{{ i }}
<div class="flex items-center border">
<div class="category">{{ category }}</div>
<div class="tags">
<div class="tag" :class="i === currentTag &&'active'"
@click="currentTag = i"
v-for="i in Object.keys(groupByTag)">{{ i }}
</div>
</div>
</div>
<DictList
@selectDict="e => emit('selectDict',e)"
:list="list"
@@ -50,8 +53,10 @@ watch(() => props.groupByTag, () => {
.category {
font-size: 1.2rem;
padding-bottom: 1rem;
border-bottom: 1px dashed gray;
//padding-bottom: 1rem;
}
.border{
border-top: 1px dashed gray;
}
}

View File

@@ -3,7 +3,6 @@
import {Dict, DictType} from "@/types.ts";
import {Icon} from "@iconify/vue";
import {$computed} from "vue/macros";
import DeleteIcon from "@/components/icon/DeleteIcon.vue";
import BaseIcon from "@/components/BaseIcon.vue";
const props = defineProps<{
@@ -63,11 +62,11 @@ let length = $computed(() => {
.dict-item {
cursor: pointer;
box-sizing: border-box;
width: 9rem;
height: 12rem;
//width: 9rem;
//height: 12rem;
position: relative;
background: var(--color-third-bg);
//background: white;
//background: var(--color-third-bg);
background: white;
border: 1px solid var(--color-item-border);
color: var(--color-font-1);
font-size: 1rem;
@@ -95,6 +94,7 @@ let length = $computed(() => {
}
.num {
margin-top: 2rem;
text-align: right;
color: var(--color-font-2);
//font-weight: bold;

View File

@@ -18,7 +18,7 @@ const emit = defineEmits<{
</script>
<template>
<div class="dict-list">
<div class="dict-list1 grid grid-cols-4 gap-4">
<DictItem v-for="(dict,index) in list"
:active="selectId === dict.id"
@click="emit('selectDict',{dict,index})"

View File

@@ -1,108 +0,0 @@
<script setup lang="ts">
import {useBaseStore} from "@/stores/base.ts";
import MiniDialog from "@/pages/pc/components/dialog/MiniDialog.vue";
import {useWindowClick} from "@/hooks/event.ts";
import {emitter, EventKey} from "@/utils/eventBus.ts";
import {nextTick, watch} from "vue";
const store = useBaseStore()
let timer = 0
let show = $ref(false)
useWindowClick(() => show = false)
function toggle(val) {
clearTimeout(timer)
if (val) {
emitter.emit(EventKey.closeOther)
show = val
} else {
timer = setTimeout(() => {
show = val
}, 100)
}
}
function clickJumpSpecifiedChapter(index: number) {
emitter.emit(EventKey.jumpSpecifiedChapter, index)
}
const listRef: HTMLElement = $ref(null as any)
watch(() => show, n => {
if (n) {
nextTick(() => {
listRef?.children[store.currentDict.chapterIndex]?.scrollIntoView({block: 'center'})
})
}
})
</script>
<template>
<div class="ChapterName" @click.stop="null">
<div class="info hvr-grow"
@mouseenter="toggle(true)"
@mouseleave="toggle(false)"
>
{{ store.chapterName }}
</div>
<MiniDialog
v-model="show"
@mouseenter="toggle(true)"
@mouseleave="toggle(false)"
style="width: 16rem;"
>
<div class="chapter-list" ref="listRef">
<div class="chapter-list-item"
:class="store.currentDict.chapterIndex === index && 'active'"
v-for="(item,index) in store.currentDict.chapterWords"
@click="clickJumpSpecifiedChapter(index)">
<input type="radio" :checked="store.currentDict.chapterIndex === index">
<div class="title">{{ index + 1 }}&nbsp;&nbsp;&nbsp;{{ item.length }}</div>
</div>
</div>
</MiniDialog>
</div>
</template>
<style scoped lang="scss">
.ChapterName {
position: relative;
}
.chapter-list {
max-height: 16rem;
overflow: auto;
padding-right: .3rem;
.chapter-list-item {
margin-bottom: .4rem;
height: 2.2rem;
display: flex;
align-items: center;
gap: .6rem;
width: 100%;
box-sizing: border-box;
background: var(--color-item-bg);
color: var(--color-font-1);
font-size: 1.1rem;
border-radius: .5rem;
transition: all .3s;
padding: .6rem;
border: 1px solid var(--color-item-border);
&:hover {
background: var(--color-item-hover);
}
&.active {
background: var(--color-item-active);
}
}
}
.el-radio-group {
display: flex;
flex-direction: column;
align-items: flex-start;
}
</style>

View File

@@ -46,7 +46,7 @@ onMounted(() => {
v-model="show"
@mouseenter="toggle(true)"
@mouseleave="toggle(false)"
style="width: 230rem;"
style="width: 15rem;"
>
<div class="mini-row-title">
单词循环设置

View File

@@ -42,7 +42,7 @@ function save() {
<template>
<div class="setting" @click.stop="null">
<Tooltip
:title="`开关释义显示(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.ToggleShowTranslate]})`"
:title="`开关释义显示(${settingStore.shortcutKeyMap[ShortcutKey.ToggleShowTranslate]})`"
>
<IconWrapper>
<Icon v-if="settingStore.translate" icon="mdi:translate"

View File

@@ -58,7 +58,7 @@ function toggle2() {
</IconWrapper>
</Tooltip>
<MiniDialog
width="250rem"
width="12rem"
v-model="show">
<div class="mini-row-title">
音效设置
@@ -75,7 +75,7 @@ function toggle2() {
</div>
</div>
<div class="mini-row">
<label class="item-title">单词/句子自动发音</label>
<label class="item-title">自动发音</label>
<div class="wrapper">
<el-switch v-model="settingStore.wordSound"
inline-prompt
@@ -85,7 +85,7 @@ function toggle2() {
</div>
</div>
<div class="mini-row">
<label class="item-title">单词/句子发音口音</label>
<label class="item-title">口音</label>
<div class="wrapper">
<el-select v-model="settingStore.wordSoundType"
placeholder="请选择"

View File

@@ -14,10 +14,10 @@ import {useSettingStore} from "@/stores/setting.ts";
import {usePracticeStore} from "@/stores/practice.ts";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {$ref} from "vue/macros";
import {DictType, ShortcutKey} from "@/types.ts";
import ChapterName from "@/pages/pc/components/toolbar/ChapterName.vue";
import {ShortcutKey} from "@/types.ts";
import {emitter, EventKey} from "@/utils/eventBus.ts";
import BaseIcon from "@/components/BaseIcon.vue";
import {useNav} from "@/utils";
const {toggleTheme} = useTheme()
const store = useBaseStore()
@@ -56,6 +56,7 @@ watch(() => store.load, n => {
}
})
const {nav} = useNav()
</script>
<template>
@@ -63,13 +64,14 @@ watch(() => store.load, n => {
<div class="content">
<div class="dict-name">
<Tooltip
:title="`词典详情(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.OpenDictDetail]})`">
<div class="info hvr-grow" @click="emitter.emit(EventKey.openDictModal,'detail')">
:title="`词典详情(${settingStore.shortcutKeyMap[ShortcutKey.OpenDictDetail]})`">
<div class="info" @click="emitter.emit(EventKey.openDictModal,'detail')">
{{ store.currentDict.name }} {{ practiceStore.repeatNumber ? ' 复习错词' : '' }}
</div>
</Tooltip>
<Icon icon="gg:arrows-exchange" />
<ChapterName v-if="store.currentDict.type === DictType.word"/>
<BaseIcon title="切换词典"
@click="nav('/dict')"
icon="gg:arrows-exchange"/>
<div class="info-text" v-if="practiceStore.repeatNumber">
复习错词
</div>
@@ -85,7 +87,7 @@ watch(() => store.load, n => {
</Tooltip>
<Tooltip
:title="`开关默写模式(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.ToggleDictation]})`"
:title="`开关默写模式(${settingStore.shortcutKeyMap[ShortcutKey.ToggleDictation]})`"
>
<IconWrapper>
<Icon icon="majesticons:eye-off-line"
@@ -103,16 +105,8 @@ watch(() => store.load, n => {
<RepeatSetting/>
<!-- <Add/>-->
<BaseIcon
@click="emitter.emit(EventKey.openDictModal,'my')"
title="添加"
icon="ic:outline-cloud-upload"/>
<Tooltip
:title="`切换主题(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.ToggleTheme]})`"
:title="`切换主题(${settingStore.shortcutKeyMap[ShortcutKey.ToggleTheme]})`"
>
<IconWrapper>
<Icon icon="ep:moon" v-if="settingStore.theme === 'dark'"
@@ -120,16 +114,9 @@ watch(() => store.load, n => {
<Icon icon="tabler:sun" v-else @click="toggleTheme"/>
</IconWrapper>
</Tooltip>
</div>
<div class="with-bg anim">
<BaseIcon
@click="runtimeStore.showSettingModal = true"
:title="`设置(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.OpenSetting]})`"
icon="uil:setting"/>
<BaseIcon
@click="settingStore.showPanel = !settingStore.showPanel"
:title="`单词本(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.TogglePanel]})`"
:title="`单词本(${settingStore.shortcutKeyMap[ShortcutKey.TogglePanel]})`"
icon="tdesign:menu-unfold"/>
</div>
</div>
@@ -211,6 +198,7 @@ header {
max-width: 45%;
font-size: 1rem;
position: relative;
gap: .5rem;
}
.hide {
@@ -232,13 +220,6 @@ header {
align-items: center;
transition: all .3s;
}
.with-bg {
display: flex;
align-items: center;
position: relative;
background: var(--color-second-bg);
}
}
}

View File

@@ -0,0 +1,185 @@
<script setup lang="ts">
import {DictResource,} from "@/types.ts";
import {$computed, $ref} from "vue/macros";
import {dictionaryResources} from "@/assets/dictionary.ts";
import {groupBy} from "lodash-es";
import {useBaseStore} from "@/stores/base.ts";
import DictList from "@/pages/pc/components/list/DictList.vue";
import DictGroup from "@/pages/pc/components/list/DictGroup.vue";
import bookFlag from "@/assets/img/flags/book.png";
import enFlag from "@/assets/img/flags/en.png";
import jaFlag from "@/assets/img/flags/ja.png";
import deFlag from "@/assets/img/flags/de.png";
import codeFlag from "@/assets/img/flags/code.png";
import myFlag from "@/assets/img/flags/my.png";
import {Icon} from "@iconify/vue";
import BaseIcon from "@/components/BaseIcon.vue";
import {useRouter} from "vue-router";
const emit = defineEmits<{
add: [],
selectDict: [val: { dict: any, index: number }]
}>()
const store = useBaseStore()
let currentLanguage = $ref('en')
let currentTranslateLanguage = $ref('common')
let groupByLanguage = groupBy(dictionaryResources, 'language')
let translateLanguageList = $ref([])
function groupByDictTags(dictList: DictResource[]) {
return dictList.reduce<Record<string, DictResource[]>>((result, dict) => {
dict.tags.forEach((tag) => {
if (Object.prototype.hasOwnProperty.call(result, tag)) {
result[tag].push(dict)
} else {
result[tag] = [dict]
}
})
return result
}, {})
}
const groupByTranslateLanguage = $computed(() => {
let data = groupBy(groupByLanguage[currentLanguage], 'translateLanguage')
// console.log('groupByTranslateLanguage', data)
translateLanguageList = Object.keys(data)
currentTranslateLanguage = translateLanguageList[0]
return data
})
const groupedByCategoryAndTag = $computed(() => {
const currentTranslateLanguageDictList = groupByTranslateLanguage[currentTranslateLanguage]
const groupByCategory = groupBy(currentTranslateLanguageDictList, 'category')
let data = []
for (const [key, value] of Object.entries(groupByCategory)) {
data.push([key, groupByDictTags(value)])
}
// console.log('groupedByCategoryAndTag', data)
return data
})
function del(e) {
store.myDictList.splice(e.index, 1)
}
const languageCategoryOptions = [
{id: 'en', name: '英语', flag: enFlag},
{id: 'ja', name: '日语', flag: jaFlag},
{id: 'de', name: '德语', flag: deFlag},
{id: 'code', name: 'Code', flag: codeFlag},
]
const router = useRouter()
</script>
<template>
<div class="dict-list-panel">
<header class="flex justify-center pb-3">
<div class="container2 flex justify-between items-center">
<div class="flex items-center gap-5">
<BaseIcon icon="ion:chevron-back" @click="router.back"/>
<div class="tabs">
<div class="tab"
:class="currentLanguage === item.id && 'active'"
@click="currentLanguage = item.id"
v-for="item in languageCategoryOptions">
<img :src='item.flag' alt=""/>
<span>{{ item.name }}</span>
</div>
</div>
</div>
<BaseIcon icon="lucide:search"/>
</div>
</header>
<div class="page-content">
<div class="dict-list-wrapper">
<div class="translate ">
<span>释义</span>
<el-radio-group v-model="currentTranslateLanguage">
<el-radio-button border v-for="i in translateLanguageList" :label="i">{{ $t(i) }}</el-radio-button>
</el-radio-group>
</div>
<DictGroup
v-for="item in groupedByCategoryAndTag"
:select-id="store.currentDict.id"
@selectDict="e => emit('selectDict',e)"
:groupByTag="item[1]"
:category="item[0]"
/>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
@import "@/assets/css/style";
.dict-list-panel {
width: 100%;
height: 100%;
$header-height: 4rem;
//padding: var(--space);
padding-top: 0;
box-sizing: border-box;
header {
position: fixed;
top: 0;
left: var(--aside-width);
width: calc(100vw - var(--aside-width));
z-index: 9;
background: var(--color-main-bg);
.tabs {
display: flex;
gap: 1.5rem;
.tab {
color: var(--color-font-1);
cursor: pointer;
padding: .3rem;
transition: all .5s;
border-bottom: 2px solid transparent;
display: flex;
align-items: center;
gap: 0.6rem;
&.active {
$main: rgb(64, 158, 255);
border-bottom: 2px solid $main;
}
img {
height: 2rem;
}
}
}
}
.page-content {
padding-top: 4rem;
display: flex;
.dict-list-wrapper {
flex: 1;
overflow: auto;
height: 100%;
.translate {
display: flex;
align-items: center;
color: var(--color-font-1);
margin-bottom: 1rem;
& > span {
font-size: 1.2rem;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,37 @@
<script setup lang="ts">
import {useBaseStore} from "@/stores/base.ts";
import DictListPanel2 from "./DictListPanel2.vue";
import {Icon} from '@iconify/vue'
import "vue-activity-calendar/style.css";
import {useRouter} from "vue-router";
const base = useBaseStore()
const router = useRouter()
function clickEvent(e) {
console.log('e', e)
}
</script>
<template>
<div class="word flex justify-center ">
<div class="container2">
<DictListPanel2
/>
</div>
</div>
</template>
<style scoped lang="scss">
.card {
@apply rounded-xl bg-white p-4 mt-5;
}
.center {
@apply flex justify-center items-center;
}
.title {
@apply text-lg font-medium;
}
</style>

View File

@@ -41,7 +41,7 @@ const router = useRouter()
</div>
<div class="bottom">
<div class="row"
:title="`设置(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.OpenSetting]})`"
:title="`设置(${settingStore.shortcutKeyMap[ShortcutKey.OpenSetting]})`"
@click="runtimeStore.showSettingModal = true">
<Icon icon="uil:setting"/>
<span>试卷</span>
@@ -66,11 +66,12 @@ const router = useRouter()
.aside {
background: white;
//position: fixed;
position: fixed;
z-index: 999;
top: 0;
left: 0;
height: 100vh;
width: 12rem;
width: var(--aside-width);
padding: 1rem 1rem;
box-sizing: border-box;
display: flex;

View File

@@ -31,30 +31,30 @@ const settingStore = useSettingStore()
v-if="!isSimple"
class="collect"
@click="$emit('toggleSimple')"
:title="`标记为简单词(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.ToggleSimple]})`"
:title="`标记为简单词(${settingStore.shortcutKeyMap[ShortcutKey.ToggleSimple]})`"
icon="material-symbols:check-circle-outline-rounded"/>
<BaseIcon
v-else
class="fill"
@click="$emit('toggleSimple')"
:title="`取消标记简单词(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.ToggleSimple]})`"
:title="`取消标记简单词(${settingStore.shortcutKeyMap[ShortcutKey.ToggleSimple]})`"
icon="material-symbols:check-circle-rounded"/>
<BaseIcon
v-if="!isCollect"
class="collect"
@click="$emit('toggleCollect')"
:title="`收藏(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.ToggleCollect]})`"
:title="`收藏(${settingStore.shortcutKeyMap[ShortcutKey.ToggleCollect]})`"
icon="ph:star"/>
<BaseIcon
v-else
class="fill"
@click="$emit('toggleCollect')"
:title="`取消收藏(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.ToggleCollect]})`"
:title="`取消收藏(${settingStore.shortcutKeyMap[ShortcutKey.ToggleCollect]})`"
icon="ph:star-fill"/>
<Tooltip
:title="`跳过(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.Next]})`"
:title="`跳过(${settingStore.shortcutKeyMap[ShortcutKey.Next]})`"
>
<IconWrapper>
<Icon icon="icon-park-outline:go-ahead" class="menu"

View File

@@ -90,13 +90,13 @@ const showCollectToggleButton = $computed(() => {
<div class="panel anim" v-show="settingStore.showPanel">
<header>
<div class="tabs">
<div class="tab" :class="tabIndex === 0 && 'active'" @click="tabIndex = 0">当前</div>
<div class="tab" :class="tabIndex === 0 && 'active'" @click="tabIndex = 0">当前学习</div>
<div class="tab" :class="tabIndex === 1 && 'active'" @click="tabIndex = 1">{{ store.collect.name }}</div>
<div class="tab" :class="tabIndex === 2 && 'active'" @click="tabIndex = 2">{{ store.simple.name }}</div>
<div class="tab" :class="tabIndex === 3 && 'active'" @click="tabIndex = 3">{{ store.wrong.name }}</div>
</div>
<Tooltip
:title="`关闭(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.TogglePanel]})`"
:title="`关闭(${settingStore.shortcutKeyMap[ShortcutKey.TogglePanel]})`"
>
<Close @click="settingStore.showPanel = false"/>
</Tooltip>

View File

@@ -343,7 +343,7 @@ defineExpose({showSentence, play, del,hideSentence,nextSentence})
<div class="options-wrapper">
<div class="flex gap10">
<BaseIcon
:title="`编辑(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.EditArticle]})`"
:title="`编辑(${settingStore.shortcutKeyMap[ShortcutKey.EditArticle]})`"
icon="tabler:edit"
@click="emit('edit',props.article)"
/>
@@ -351,16 +351,16 @@ defineExpose({showSentence, play, del,hideSentence,nextSentence})
v-if="!isArticleCollect(props.article)"
class="collect"
@click="toggleArticleCollect(props.article)"
:title="`收藏(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.ToggleCollect]})`"
:title="`收藏(${settingStore.shortcutKeyMap[ShortcutKey.ToggleCollect]})`"
icon="ph:star"/>
<BaseIcon
v-else
class="fill"
@click="toggleArticleCollect(props.article)"
:title="`取消收藏(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.ToggleCollect]})`"
:title="`取消收藏(${settingStore.shortcutKeyMap[ShortcutKey.ToggleCollect]})`"
icon="ph:star-fill"/>
<BaseIcon
:title="`跳过(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.Next]})`"
:title="`跳过(${settingStore.shortcutKeyMap[ShortcutKey.Next]})`"
icon="icon-park-outline:go-ahead"
@click="emit('over')"/>
</div>

View File

@@ -359,7 +359,7 @@ defineExpose({getCurrentPractice})
{{ store.currentDict.name }}
</div>
<Tooltip
:title="`下一章(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.NextChapter]})`"
: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"/>

View File

@@ -166,7 +166,7 @@ defineExpose({del, showWord, hideWord, play})
<!-- <div class="volumeIcon">-->
<!-- <Tooltip-->
<!-- v-if="i === word.trans.length - 1"-->
<!-- :title="`发音(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.PlayTranslatePronunciation]})`"-->
<!-- :title="`发音(${settingStore.shortcutKeyMap[ShortcutKey.PlayTranslatePronunciation]})`"-->
<!-- >-->
<!-- <VolumeIcon-->
<!-- ref="volumeTranslateIconRef"-->
@@ -193,7 +193,7 @@ defineExpose({del, showWord, hideWord, play})
<span class="letter" v-else>{{ displayWord }}</span>
</div>
<Tooltip
:title="`发音(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.PlayWordPronunciation]})`"
:title="`发音(${settingStore.shortcutKeyMap[ShortcutKey.PlayWordPronunciation]})`"
>
<VolumeIcon ref="volumeIconRef" :simple="true" :cb="() => playWordAudio(word.word)"/>
</Tooltip>

View File

@@ -247,7 +247,7 @@ onUnmounted(() => {
v-if="prevWord">
<Icon class="arrow" icon="bi:arrow-left" width="22"/>
<Tooltip
:title="`上一个(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.Previous]})`"
:title="`上一个(${settingStore.shortcutKeyMap[ShortcutKey.Previous]})`"
>
<div class="word">{{ prevWord.word }}</div>
</Tooltip>
@@ -256,7 +256,7 @@ onUnmounted(() => {
@click="next(false)"
v-if="nextWord">
<Tooltip
:title="`下一个(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.Next]})`"
:title="`下一个(${settingStore.shortcutKeyMap[ShortcutKey.Next]})`"
>
<div class="word" :class="settingStore.dictation && 'text-shadow'">{{ nextWord.word }}</div>
</Tooltip>
@@ -288,40 +288,26 @@ onUnmounted(() => {
v-loading="!store.load"
>
<div class="list-header">
<div class="left">
<div class="title">
{{ store.chapterName }}
</div>
<BaseIcon title="切换词典"
@click="emitter.emit(EventKey.openDictModal,'list')"
icon="carbon:change-catalog"/>
<div style="position:relative;"
@click.stop="null">
<BaseIcon
title="改变顺序"
icon="icon-park-outline:sort-two"
@click="showSortOption = !showSortOption"
/>
<MiniDialog
v-model="showSortOption"
style="width: 130rem;"
>
<div class="mini-row-title">
列表循环设置
</div>
<div class="mini-row">
<BaseButton size="small" @click="sort(Sort.reverse)">翻转</BaseButton>
<BaseButton size="small" @click="sort(Sort.random)">随机</BaseButton>
</div>
</MiniDialog>
</div>
<BaseIcon icon="bi:arrow-right"
@click="emitter.emit(EventKey.next)"
:title="`下一章(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.NextChapter]})`"
v-if="store.currentDict.chapterIndex < store.currentDict.chapterWords.length - 1"/>
</div>
<div class="right">
{{ data.words.length }}个单词
<div>{{ data.words.length }}个单词</div>
<div style="position:relative;"
@click.stop="null">
<BaseIcon
title="改变顺序"
icon="icon-park-outline:sort-two"
@click="showSortOption = !showSortOption"
/>
<MiniDialog
v-model="showSortOption"
style="width: 9rem;"
>
<div class="mini-row-title">
列表循环设置
</div>
<div class="mini-row">
<BaseButton size="small" @click="sort(Sort.reverse)">翻转</BaseButton>
<BaseButton size="small" @click="sort(Sort.random)">随机</BaseButton>
</div>
</MiniDialog>
</div>
</div>
<WordList
@@ -396,13 +382,12 @@ onUnmounted(() => {
align-items: center;
.arrow {
min-width: 1.6rem;
min-height: 1.6rem;
font-size: .5rem;
}
}
.word {
font-size: 1.8rem;
font-size: 1.2rem;
margin-bottom: .2rem;
font-family: var(--word-font-family);
}

View File

@@ -5,6 +5,7 @@ import {Icon} from '@iconify/vue'
import {ActivityCalendar} from "vue-activity-calendar";
import "vue-activity-calendar/style.css";
import {useRouter} from "vue-router";
import BaseIcon from "@/components/BaseIcon.vue";
const base = useBaseStore()
const router = useRouter()
@@ -65,24 +66,12 @@ function clickEvent(e) {
</div>
</div>
<div class="card ">
<div class="flex justify-between ">
<div class="title">
所有词典
</div>
<Icon icon="lucide:search" class="text-2xl" />
</div>
<div class="bg-white ">
<DictListPanel
/>
</div>
</div>
<div class="card">
<div class="title">
其他学习词典
<div class="flex justify-between">
<div class="title">
其他学习词典
</div>
<BaseIcon icon="ic:round-add" @click="router.push('/dict')"/>
</div>
<div class="grid grid-cols-2 gap-6 mt-5 ">
<div class=" p-4 rounded-md justify-between items-center bg-slate-200 " v-for="i in 3">

View File

@@ -19,6 +19,7 @@ import MusicSetting from "@/pages/mobile/my/setting/MusicSetting.vue";
import OtherSetting from "@/pages/mobile/my/setting/OtherSetting.vue";
import WordHome from "@/pages/pc/word/WordHome.vue";
import PC from "@/pages/pc/index.vue";
import Dict2 from '@/pages/pc/dict2/index.vue'
export const routes: RouteRecordRaw[] = [
{
@@ -26,6 +27,7 @@ export const routes: RouteRecordRaw[] = [
redirect: '/word',
children: [
{path: 'word', component: WordHome},
{path: 'dict', component: Dict2},
{path: 'practice', component: Practice},
]
},

View File

@@ -14,6 +14,7 @@ export const EventKey = {
keyup: 'keyup',
onTyping: 'onTyping',
repeat: 'repeat',
//TODO 废弃
next: 'next',
write: 'write',
editDict: 'editDict',

View File

@@ -4,6 +4,7 @@ import {DefaultSettingState, SettingState} from "@/stores/setting.ts";
import {cloneDeep} from "lodash-es";
import {Dict, DictType} from "@/types.ts";
import {ArchiveReader, libarchiveWasm} from "libarchive-wasm";
import {useRouter} from "vue-router";
export function getRandom(a: number, b: number): number {
return Math.random() * (b - a) + a;
@@ -186,3 +187,13 @@ export function getDictFile(url: string) {
}
})
}
export function useNav() {
const router = useRouter()
function nav(val) {
router.push(val)
}
return {nav, back: router.back}
}