Modify the word list component

This commit is contained in:
zyronon
2023-11-12 15:04:56 +08:00
parent 5d802d4faf
commit 60e3975b3e
13 changed files with 363 additions and 190 deletions

View File

@@ -54,3 +54,4 @@ http://enpuz.com/ 语法分析工具
点击句子播放的音乐,需要可暂停
footer 的输入数统计有问题,当在列表点一个,然后输入错误之后,不会统计到输入数里面(单词和文章的都有问题)

2
components.d.ts vendored
View File

@@ -17,6 +17,7 @@ declare module 'vue' {
ChapterDetail: typeof import('./src/components/ChapterDetail.vue')['default']
ChapterList: typeof import('./src/components/list/ChapterList.vue')['default']
Close: typeof import('./src/components/icon/Close.vue')['default']
CommonWordList: typeof import('./src/components/list/CommonWordList.vue')['default']
DictGroup: typeof import('./src/components/Toolbar/DictGroup.vue')['default']
DictList: typeof import('./src/components/list/DictList.vue')['default']
DictModal: typeof import('./src/components/Modal/DictModal.vue')['default']
@@ -64,6 +65,7 @@ declare module 'vue' {
Typing: typeof import('./src/components/Practice/PracticeWord/Typing.vue')['default']
TypingArticle: typeof import('./src/components/Practice/PracticeArticle/TypingArticle.vue')['default']
TypingWord: typeof import('./src/components/Practice/PracticeWord/TypingWord.vue')['default']
VirtualWordList: typeof import('./src/components/list/VirtualWordList.vue')['default']
VolumeIcon: typeof import('./src/components/icon/VolumeIcon.vue')['default']
VolumeSetting: typeof import('./src/components/Toolbar/VolumeSetting.vue')['default']
WordItem: typeof import('./src/components/list/WordItem.vue')['default']

View File

@@ -38,7 +38,6 @@ watch(store.wrong.originWords, (n) => {
store.wrong.chapterWords = [store.wrong.words]
})
useStartKeyboardEventListener()
async function init() {
console.time()
@@ -54,6 +53,8 @@ onMounted(() => {
init()
})
useStartKeyboardEventListener()
</script>
<template>

View File

@@ -271,28 +271,44 @@ footer {
gap: 20rem;
border: 1px solid var(--color-item-border);
.volume {
.left {
display: flex;
gap: 3rem;
flex-direction: column;
word-break: break-word;
}
.right {
display: flex;
flex-direction: column;
gap: 5rem;
transition: all .3s;
}
.volume, .collect, .easy {
opacity: 0;
}
&:hover {
background: var(--color-item-hover);
.volume {
.volume, .collect, .easy {
opacity: 1;
}
}
&.active {
background: var(--color-item-active);
.volume {
color: #E6A23C;
}
$c: #E6A23C;
.phonetic, .item-sub-title {
color: var(--color-gray) !important;
}
.volume, .collect, .easy, .fill {
color: $c;
}
}
.item-title {
@@ -325,7 +341,6 @@ footer {
user-select: none;
}
.common-title {
font-size: 20rem;
color: var(--color-font-1);

View File

@@ -15,9 +15,11 @@ import {dictionaryResources} from "@/assets/dictionary.ts";
import {cloneDeep} from "lodash-es";
import Empty from "@/components/Empty.vue";
import BaseIcon from "@/components/BaseIcon.vue";
import WordList2 from "@/components/list/WordList2.vue";
import VirtualWordList from "@/components/list/VirtualWordList.vue";
import Modal from "@/components/Modal/Modal.vue";
import {emitter, EventKey} from "@/utils/eventBus.ts";
useDisableEventListener()
// useDisableEventListener()
const store = useBaseStore()
const settingStore = useSettingStore()
@@ -233,181 +235,194 @@ watch(() => step, v => {
}
})
let show = $ref(false)
function close() {
emit('close')
show = false
}
onMounted(() => {
emitter.on(EventKey.editDict, (dict: Dict) => {
show = true
})
})
</script>
<template>
<div id="AddWordDialog" :class="wordFormMode !== FormMode.None && 'add-word-mode'">
<Slide :slide-count="2" :step="step">
<div class="page dict-list-page">
<header>
<div class="title">
我的词典
</div>
<Icon @click="close"
class="hvr-grow pointer"
width="20" color="#929596"
icon="ion:close-outline"/>
</header>
<div class="list">
<DictList
@add="step = 1;isAddDict = true"
@selectDict="selectDict"
:list="dictList"/>
</div>
</div>
<div class="page add-page">
<header>
<div class="left" @click.stop="step = 0">
<Icon icon="octicon:arrow-left-24" class="go" width="20"/>
<Modal
:header="false"
v-model="show"
:show-close="false">
<div id="AddWordDialog" :class="wordFormMode !== FormMode.None && 'add-word-mode'">
<Slide :slide-count="2" :step="step">
<div class="page dict-list-page">
<header>
<div class="title">
词典详情
我的词典
</div>
<Icon @click="close"
class="hvr-grow pointer"
width="20" color="#929596"
icon="ion:close-outline"/>
</header>
<div class="list">
<DictList
@add="step = 1;isAddDict = true"
@selectDict="selectDict"
:list="dictList"/>
</div>
<Icon @click="close"
class="hvr-grow pointer"
width="20" color="#929596"
icon="ion:close-outline"/>
</header>
<div class="detail" v-if="!isAddDict">
<div class="dict">
<div class="info">
<div class="name">{{ store.editDict.name }}</div>
<div class="desc">{{ store.editDict.description }}</div>
<div class="more-info">
<div class="item">词汇量{{ store.editDict.originWords.length }}</div>
<div class="item">创建日期-</div>
<div class="item">花费时间-</div>
<div class="item">累积错误-</div>
</div>
<div class="page add-page">
<header>
<div class="left" @click.stop="step = 0">
<Icon icon="octicon:arrow-left-24" class="go" width="20"/>
<div class="title">
词典详情
</div>
<BaseIcon
class-name="edit-icon"
icon="tabler:edit"
@click='editDict'
/>
</div>
<div class="add" v-if="wordFormMode !== FormMode.None">
<div class="common-title">{{ wordFormMode === FormMode.Add ? '添加' : '修改' }}单词</div>
<el-form
class="form"
ref="wordFormRef"
:rules="wordRules"
:model="wordForm"
label-width="140rem">
<el-form-item label="单词" prop="name">
<el-input v-model="wordForm.name"/>
</el-form-item>
<el-form-item label="翻译">
<el-input v-model="wordForm.trans"
placeholder="多个翻译请换行"
:autosize="{ minRows: 2, maxRows: 6 }"
type="textarea"/>
</el-form-item>
<el-form-item label="音标/发音/注音①">
<el-input v-model="wordForm.usphone"/>
</el-form-item>
<el-form-item label="音标/发音/注音②">
<el-input v-model="wordForm.ukphone"/>
</el-form-item>
<div class="flex-center">
<el-button @click="closeWordForm">关闭</el-button>
<el-button type="primary" @click="onSubmitWord">{{
wordFormMode === FormMode.Add ? '添加' : '保存'
}}
</el-button>
<Icon @click="close"
class="hvr-grow pointer"
width="20" color="#929596"
icon="ion:close-outline"/>
</header>
<div class="detail" v-if="!isAddDict">
<div class="dict">
<div class="info">
<div class="name">{{ store.editDict.name }}</div>
<div class="desc">{{ store.editDict.description }}</div>
<div class="more-info">
<div class="item">词汇量{{ store.editDict.originWords.length }}</div>
<div class="item">创建日期-</div>
<div class="item">花费时间-</div>
<div class="item">累积错误-</div>
</div>
</el-form>
</div>
</div>
<div class="list-wrapper">
<div class="list-header">
<div class="name">单词列表</div>
<div class="flex-center gap10">
<div class="name">{{ wordList.length }}个单词</div>
<BaseIcon icon="mi:add"
@click='addWord'
<BaseIcon
v-if="![DictType.collect,DictType.wrong,DictType.simple].includes(store.editDict.type)"
class-name="edit-icon"
icon="tabler:edit"
@click='editDict'
/>
</div>
<div class="add" v-if="wordFormMode !== FormMode.None">
<div class="common-title">{{ wordFormMode === FormMode.Add ? '添加' : '修改' }}单词</div>
<el-form
class="form"
ref="wordFormRef"
:rules="wordRules"
:model="wordForm"
label-width="140rem">
<el-form-item label="单词" prop="name">
<el-input v-model="wordForm.name"/>
</el-form-item>
<el-form-item label="翻译">
<el-input v-model="wordForm.trans"
placeholder="多个翻译请换行"
:autosize="{ minRows: 2, maxRows: 6 }"
type="textarea"/>
</el-form-item>
<el-form-item label="音标/发音/注音①">
<el-input v-model="wordForm.usphone"/>
</el-form-item>
<el-form-item label="音标/发音/注音②">
<el-input v-model="wordForm.ukphone"/>
</el-form-item>
<div class="flex-center">
<el-button @click="closeWordForm">关闭</el-button>
<el-button type="primary" @click="onSubmitWord">{{
wordFormMode === FormMode.Add ? '添加' : '保存'
}}
</el-button>
</div>
</el-form>
</div>
</div>
<WordList2
ref="wordListRef"
v-if="wordList.length"
class="word-list"
:is-active="true"
@change="editWord"
:list="wordList"
:activeIndex="wordFormMode">
<template v-slot="{word,index}">
<BaseIcon
class-name="del"
@click="delWord(index)"
title="移除"
icon="solar:trash-bin-minimalistic-linear"/>
</template>
</WordList2>
<Empty v-else/>
<div class="list-wrapper">
<div class="list-header">
<div class="name">单词列表</div>
<div class="flex-center gap10">
<div class="name">{{ wordList.length }}个单词</div>
<BaseIcon icon="mi:add"
@click='addWord'
/>
</div>
</div>
<VirtualWordList
ref="wordListRef"
v-if="wordList.length"
class="word-list"
:is-active="true"
@change="editWord"
:list="wordList"
:activeIndex="wordFormMode">
<template v-slot="{word,index}">
<BaseIcon
class-name="del"
@click="delWord(index)"
title="移除"
icon="solar:trash-bin-minimalistic-linear"/>
</template>
</VirtualWordList>
<Empty v-else/>
</div>
</div>
<div class="edit" v-else>
<div class="common-title">{{ dictForm.id ? '修改' : '添加' }}词典</div>
<el-form
ref="dictFormRef"
:rules="dictRules"
:model="dictForm"
label-width="120rem">
<el-form-item label="名称" prop="name">
<el-input v-model="dictForm.name"/>
</el-form-item>
<el-form-item label="描述">
<el-input v-model="dictForm.description" type="textarea"/>
</el-form-item>
<el-form-item label="语言">
<el-select v-model="dictForm.language" placeholder="请选择选项">
<el-option label="英语" value="en"/>
<el-option label="德语" value="de"/>
<el-option label="日语" value="ja"/>
<el-option label="代码" value="code"/>
</el-select>
</el-form-item>
<el-form-item label="翻译语言">
<el-select v-model="dictForm.translateLanguage" placeholder="请选择选项">
<!-- <el-option label="通用" value="common"/>-->
<el-option label="中文" value="zh-CN"/>
<el-option label="英语" value="en"/>
<el-option label="德语" value="de"/>
<el-option label="日语" value="ja"/>
</el-select>
</el-form-item>
<el-form-item label="分类" prop="category">
<el-select v-model="dictForm.category" placeholder="请选择选项">
<el-option :label="i" :value="i" v-for="i in categoryList[dictForm.language]"/>
</el-select>
</el-form-item>
<el-form-item label="标签" prop="tags">
<el-select
multiple
v-model="dictForm.tags" placeholder="请选择选项">
<el-option :label="i" :value="i" v-for="i in tagList[dictForm.category]"/>
</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>
</el-form-item>
<div class="flex-center">
<el-button @click="closeDictForm">关闭</el-button>
<el-button type="primary" @click="onSubmit">确定</el-button>
</div>
</el-form>
</div>
</div>
<div class="edit" v-else>
<div class="common-title">{{ dictForm.id ? '修改' : '添加' }}词典</div>
<el-form
ref="dictFormRef"
:rules="dictRules"
:model="dictForm"
label-width="120rem">
<el-form-item label="名称" prop="name">
<el-input v-model="dictForm.name"/>
</el-form-item>
<el-form-item label="描述">
<el-input v-model="dictForm.description" type="textarea"/>
</el-form-item>
<el-form-item label="语言">
<el-select v-model="dictForm.language" placeholder="请选择选项">
<el-option label="英语" value="en"/>
<el-option label="德语" value="de"/>
<el-option label="日语" value="ja"/>
<el-option label="代码" value="code"/>
</el-select>
</el-form-item>
<el-form-item label="翻译语言">
<el-select v-model="dictForm.translateLanguage" placeholder="请选择选项">
<!-- <el-option label="通用" value="common"/>-->
<el-option label="中文" value="zh-CN"/>
<el-option label="英语" value="en"/>
<el-option label="德语" value="de"/>
<el-option label="日语" value="ja"/>
</el-select>
</el-form-item>
<el-form-item label="分类" prop="category">
<el-select v-model="dictForm.category" placeholder="请选择选项">
<el-option :label="i" :value="i" v-for="i in categoryList[dictForm.language]"/>
</el-select>
</el-form-item>
<el-form-item label="标签" prop="tags">
<el-select
multiple
v-model="dictForm.tags" placeholder="请选择选项">
<el-option :label="i" :value="i" v-for="i in tagList[dictForm.category]"/>
</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>
</el-form-item>
<div class="flex-center">
<el-button @click="closeDictForm">关闭</el-button>
<el-button type="primary" @click="onSubmit">确定</el-button>
</div>
</el-form>
</div>
</div>
</Slide>
</div>
</Slide>
</div>
</Modal>
</template>
<style scoped lang="scss">

View File

@@ -15,6 +15,8 @@ import {useWordOptions} from "@/hooks/dict.ts";
import {Icon} from "@iconify/vue";
import Tooltip from "@/components/Tooltip.vue";
import IconWrapper from "@/components/IconWrapper.vue";
import CommonWordList from "@/components/list/CommonWordList.vue";
import BaseIcon from "@/components/BaseIcon.vue";
const store = useBaseStore()
const settingStore = useSettingStore()
@@ -38,9 +40,11 @@ function changeIndex(i: number, dict: Dict) {
const {
delWrongWord,
delSimpleWord
delSimpleWord,
toggleWordCollect,
} = useWordOptions()
</script>
<template>
<Transition name="fade">
@@ -100,10 +104,18 @@ const {
</template>
</div>
<template v-if="practiceType === DictType.word">
<WordList
<CommonWordList
v-if="store.collect.words.length"
class="word-list"
:list="store.collect.words"/>
:list="store.collect.words">
<template v-slot="{word,index}">
<BaseIcon
class-name="del"
@click="toggleWordCollect(word)"
title="移除"
icon="solar:trash-bin-minimalistic-linear"/>
</template>
</CommonWordList>
<Empty v-else/>
</template>
<template v-else>
@@ -129,11 +141,17 @@ const {
</PopConfirm>
</template>
</div>
<WordList
<CommonWordList
class="word-list"
:show-del="true"
@del="delSimpleWord"
:list="store.simple.words"/>
:list="store.simple.words">
<template v-slot="{word,index}">
<BaseIcon
class-name="del"
@click="delSimpleWord(word)"
title="移除"
icon="solar:trash-bin-minimalistic-linear"/>
</template>
</CommonWordList>
</div>
<Empty v-else/>
</div>
@@ -151,11 +169,17 @@ const {
</PopConfirm>
</template>
</div>
<WordList
<CommonWordList
class="word-list"
:show-del="true"
@del="delWrongWord"
:list="store.wrong.words"/>
:list="store.wrong.words">
<template v-slot="{word,index}">
<BaseIcon
class-name="del"
@click="delWrongWord(word)"
title="移除"
icon="solar:trash-bin-minimalistic-linear"/>
</template>
</CommonWordList>
</div>
<Empty v-else/>
</div>

View File

@@ -158,7 +158,7 @@ onUnmounted(() => {
<PracticeWord ref="practiceRef" v-else/>
<Footer/>
</div>
<!-- <AddWordDialog></AddWordDialog>-->
<AddWordDialog></AddWordDialog>
<DictModal :model-value="runtimeStore.showDictModal" @close="runtimeStore.showDictModal = false"/>
<SettingModal v-if="runtimeStore.showSettingModal" @close="runtimeStore.showSettingModal = false"/>
<Statistics/>

View File

@@ -198,7 +198,7 @@ defineExpose({del, showWord, hideWord, play})
<VolumeIcon ref="volumeIconRef" :simple="true" :cb="() => playWordAudio(word.name)"/>
</Tooltip>
</div>
<div class="phonetic">{{ settingStore.wordSoundType === 'us' ? word.usphone : word.ukphone }}</div>
<div class="phonetic">[{{ settingStore.wordSoundType === 'us' ? word.usphone : word.ukphone }}]</div>
</div>
</template>

View File

@@ -26,6 +26,8 @@ import WordList from "@/components/list/WordList.vue";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {useWordOptions} from "@/hooks/dict.ts";
import {usePlayWordAudio} from "@/hooks/sound.ts";
import BaseIcon from "@/components/BaseIcon.vue";
import CommonWordList from "@/components/list/CommonWordList.vue";
interface IProps {
words: Word[],
@@ -172,9 +174,9 @@ useOnKeyboardEventListener(onKeyDown, onKeyUp)
//TODO 略过忽略的单词上
function prev() {
if (data.index === 0){
if (data.index === 0) {
ElMessage.warning('已经是第一个了~')
}else {
} else {
data.index--
}
}
@@ -296,14 +298,39 @@ onUnmounted(() => {
{{ data.words.length }}个单词
</div>
</div>
<WordList
<CommonWordList
class="word-list"
:is-active="active"
@change="(i:number) => data.index = i"
@change="(val:any) => data.index = val.index"
:show-word="!settingStore.dictation"
:show-translate="settingStore.translate"
:list="data.words"
:activeIndex="data.index"/>
:activeIndex="data.index">
<template v-slot="{word,index}">
<BaseIcon
v-if="!isWordCollect(word)"
class-name="collect"
@click="toggleWordCollect(word)"
title="收藏" icon="ph:star"/>
<BaseIcon
v-else
class-name="fill"
@click="toggleWordCollect(word)"
title="取消收藏" icon="ph:star-fill"/>
<BaseIcon
v-if="!isWordSimple(word)"
class-name="easy"
@click="toggleWordCollect(word)"
title="标记为简单词"
icon="material-symbols:check-circle-outline-rounded"/>
<BaseIcon
v-else
class-name="fill"
@click="toggleWordCollect(word)"
title="取消标记简单词"
icon="material-symbols:check-circle-rounded"/>
</template>
</CommonWordList>
</div>
</template>
</Panel>

View File

@@ -0,0 +1,89 @@
<script setup lang="ts">
import {Word} from "../../types.ts";
import {useSettingStore} from "@/stores/setting.ts";
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
import {usePlayWordAudio} from "@/hooks/sound.ts";
import {watch} from 'vue'
const props = withDefaults(defineProps<{
list: Word[],
activeIndex?: number,
isActive?: boolean
showTranslate?: boolean
showWord?: boolean
}>(), {
activeIndex: -1,
isActive: false,
showTranslate: true,
showWord: true
})
const emit = defineEmits<{
change: [val: { word: Word, index: number }],
}>()
const settingStore = useSettingStore()
const listRef: HTMLElement = $ref(null as any)
function scrollViewToCenter(index: number) {
if (index === -1) return
listRef.children[index]?.scrollIntoView({block: 'center', behavior: 'smooth'})
}
watch(() => props.activeIndex, (n: any) => {
if (settingStore.showPanel) {
scrollViewToCenter(n)
}
})
watch(() => props.isActive, (n: boolean) => {
setTimeout(() => {
if (n) scrollViewToCenter(props.activeIndex)
}, 300)
})
watch(() => props.list, () => {
listRef.scrollTo(0, 0)
})
const playWordAudio = usePlayWordAudio()
</script>
<template>
<div class="common-list" ref="listRef">
<div class="common-list-item"
v-for="(source,index) in list" :key="index"
:class="{active:activeIndex === index}"
@click="emit('change',{word:source,index})"
>
<div class="left">
<div class="item-title">
<span class="word" :class="!showWord && 'text-shadow'">{{ source.name }}</span>
<span class="phonetic">{{ source.usphone }}</span>
<VolumeIcon class="volume" @click="playWordAudio(source.name)"></VolumeIcon>
</div>
<div class="item-sub-title" v-if="source.trans.length && showTranslate">
<div v-for="item in source.trans">{{ item }}</div>
</div>
</div>
<div class="right">
<slot :word="source" :index="index"></slot>
</div>
</div>
</div>
</template>
<style lang="scss">
@import "@/assets/css/variable";
.common-list {
display: flex;
flex-direction: column;
gap: 15rem;
flex: 1;
overflow: overlay;
padding: 0 var(--space);
}
</style>

View File

@@ -9,20 +9,17 @@ import {watch} from 'vue'
const props = withDefaults(defineProps<{
list: Word[],
activeIndex?: number,
showDel?: boolean,
isActive?: boolean
showTranslate?: boolean
showWord?: boolean
}>(), {
activeIndex: -1,
isActive: false,
showDel: false,
showTranslate: true,
showWord: true
})
const emit = defineEmits<{
del: [val: Word],
change: [val: { word: Word, index: number }],
}>()

View File

@@ -74,7 +74,7 @@ const {
:isCollect="isWordCollect(word)"
@toggle-collect="toggleWordCollect(word)"
:is-simple="isWordSimple(word)"
@toggle-simple="toggleWordSimple(word)"
@toggle-simple="toggleWordCollect(word)"
:show-del="showDel"
@del="emit('del',word)"
>

View File

@@ -13,4 +13,6 @@ export const EventKey = {
repeat: 'repeat',
next: 'next',
write: 'write',
editDict: 'editDict',
openMyDictDialog: 'openMyDictDialog',
}