reconfiguration
This commit is contained in:
18
components.d.ts
vendored
18
components.d.ts
vendored
@@ -9,20 +9,26 @@ declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
Add: typeof import('./src/components/Toolbar/Add.vue')['default']
|
||||
AddDict: typeof import('./src/components/Add/AddDict.vue')['default']
|
||||
AddWord: typeof import('./src/components/Modal/AddWord.vue')['default']
|
||||
AddWordDialog: typeof import('./src/components/Modal/AddWordDialog.vue')['default']
|
||||
AddWordModal: typeof import('./src/components/Modal/AddWordModal.vue')['default']
|
||||
ArticleList: typeof import('./src/components/Article/ArticleList.vue')['default']
|
||||
Backgorund: typeof import('./src/components/Backgorund.vue')['default']
|
||||
BaseButton: typeof import('./src/components/BaseButton.vue')['default']
|
||||
BaseIcon: typeof import('./src/components/BaseIcon.vue')['default']
|
||||
ChapterDetail: typeof import('./src/components/ChapterDetail.vue')['default']
|
||||
ChapterList: typeof import('./src/components/ChapterList.vue')['default']
|
||||
Close: typeof import('./src/components/Close.vue')['default']
|
||||
Close: typeof import('./src/components/icon/Close.vue')['default']
|
||||
DictGroup: typeof import('./src/components/Toolbar/DictGroup.vue')['default']
|
||||
DictList: typeof import('./src/components/DictList.vue')['default']
|
||||
DictModal: typeof import('./src/components/Toolbar/DictModal.vue')['default']
|
||||
DictModal: typeof import('./src/components/Modal/DictModal.vue')['default']
|
||||
EditAbleText: typeof import('./src/components/EditAbleText.vue')['default']
|
||||
EditArticle: typeof import('./src/components/Article/EditArticle.vue')['default']
|
||||
EditBatchArticleModal: typeof import('./src/components/Article/EditBatchArticleModal.vue')['default']
|
||||
EditSingleArticleModal: typeof import('./src/components/Article/EditSingleArticleModal.vue')['default']
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElForm: typeof import('element-plus/es')['ElForm']
|
||||
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
||||
ElInput: typeof import('element-plus/es')['ElInput']
|
||||
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
|
||||
ElOption: typeof import('element-plus/es')['ElOption']
|
||||
@@ -41,7 +47,7 @@ declare module 'vue' {
|
||||
Input: typeof import('./src/components/Input.vue')['default']
|
||||
List: typeof import('./src/components/List.vue')['default']
|
||||
ListItem: typeof import('./src/components/ListItem.vue')['default']
|
||||
MiniModal: typeof import('./src/components/MiniModal.vue')['default']
|
||||
MiniModal: typeof import('./src/components/Modal/MiniModal.vue')['default']
|
||||
Modal: typeof import('./src/components/Modal/Modal.vue')['default']
|
||||
Options: typeof import('./src/components/Practice/Options.vue')['default']
|
||||
Panel: typeof import('./src/components/Practice/Panel.vue')['default']
|
||||
@@ -51,15 +57,17 @@ declare module 'vue' {
|
||||
PracticeWord: typeof import('./src/components/Practice/PracticeWord/PracticeWord.vue')['default']
|
||||
RepeatSetting: typeof import('./src/components/Toolbar/RepeatSetting.vue')['default']
|
||||
Ring: typeof import('./src/components/Ring.vue')['default']
|
||||
SettingModal: typeof import('./src/components/Toolbar/SettingModal.vue')['default']
|
||||
SettingModal: typeof import('./src/components/Modal/SettingModal.vue')['default']
|
||||
Slide: typeof import('./src/components/Slide.vue')['default']
|
||||
Statistics: typeof import('./src/components/Practice/Statistics.vue')['default']
|
||||
Swiper: typeof import('./src/components/Swiper.vue')['default']
|
||||
Toolbar: typeof import('./src/components/Toolbar/Toolbar.vue')['default']
|
||||
Tooltip: typeof import('./src/components/Tooltip.vue')['default']
|
||||
TranslateSetting: typeof import('./src/components/Toolbar/TranslateSetting.vue')['default']
|
||||
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']
|
||||
VolumeIcon: typeof import('./src/components/VolumeIcon.vue')['default']
|
||||
VolumeIcon: typeof import('./src/components/icon/VolumeIcon.vue')['default']
|
||||
VolumeSetting: typeof import('./src/components/Toolbar/VolumeSetting.vue')['default']
|
||||
WordList: typeof import('./src/components/WordList.vue')['default']
|
||||
WordListModal: typeof import('./src/components/WordListModal.vue')['default']
|
||||
|
||||
@@ -38,7 +38,7 @@ watch(store.wrong.originWords, (n) => {
|
||||
store.wrong.chapterWords = [store.wrong.words]
|
||||
})
|
||||
|
||||
useStartKeyboardEventListener()
|
||||
// useStartKeyboardEventListener()
|
||||
|
||||
onMounted(() => {
|
||||
console.time()
|
||||
|
||||
@@ -39,12 +39,12 @@ html.dark {
|
||||
--color-background: transparent;
|
||||
--color-main-bg: rgba(14, 18, 23, 1);
|
||||
//--color-main-bg: rgba(30,31,34, 1);
|
||||
--color-second-bg: rgb(43,45,48);
|
||||
--color-second-bg: rgb(30,31,34);
|
||||
--color-second-bg: rgb(43, 45, 48);
|
||||
--color-second-bg: rgb(30, 31, 34);
|
||||
|
||||
--color-item-bg: rgb(43,45,48);
|
||||
--color-item-hover: rgb(67,69,74);
|
||||
--color-item-active: rgb(84,84,84);
|
||||
--color-item-bg: rgb(43, 45, 48);
|
||||
--color-item-hover: rgb(67, 69, 74);
|
||||
--color-item-active: rgb(84, 84, 84);
|
||||
--color-item-border: rgb(41, 41, 41);
|
||||
|
||||
--color-header-bg: rgb(51, 51, 51);
|
||||
@@ -56,9 +56,9 @@ html.dark {
|
||||
|
||||
--color-gray: #bebebe;
|
||||
|
||||
--color-scrollbar: rgb(59, 87, 138);
|
||||
--color-scrollbar: rgb(77,78,81);
|
||||
--color-scrollbar: rgb(92,93,94);
|
||||
--color-scrollbar: rgb(59, 87, 138);
|
||||
--color-scrollbar: rgb(77, 78, 81);
|
||||
--color-scrollbar: rgb(92, 93, 94);
|
||||
}
|
||||
|
||||
$anim-time: 0.3s;
|
||||
@@ -155,7 +155,7 @@ footer {
|
||||
gap: 10rem;
|
||||
}
|
||||
|
||||
.justify-content-between {
|
||||
.space-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
@@ -172,7 +172,7 @@ footer {
|
||||
padding-bottom: $space;
|
||||
box-sizing: border-box;
|
||||
|
||||
header {
|
||||
.list-header {
|
||||
min-height: 50rem;
|
||||
padding: 10rem $space;
|
||||
box-sizing: border-box;
|
||||
|
||||
@@ -1,21 +1,29 @@
|
||||
<script setup lang="ts">
|
||||
import Modal from "@/components/Modal/Modal.vue"
|
||||
import {Icon} from '@iconify/vue';
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import DictList from "@/components/DictList.vue";
|
||||
import {Dict, DictType, languageCategoryOptions, Sort} from "@/types.ts";
|
||||
|
||||
import WordList from "@/components/WordList.vue";
|
||||
import {$computed, $ref} from "vue/macros";
|
||||
import Slide from "@/components/Slide.vue";
|
||||
import DictList from "@/components/DictList.vue";
|
||||
import {Icon} from "@iconify/vue";
|
||||
import {useBaseStore} from "@/stores/base.ts";
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import {useRuntimeStore} from "@/stores/runtime.ts";
|
||||
import {useDisableEventListener} from "@/hooks/event.ts";
|
||||
import {Dict, DictType, languageCategoryOptions, Sort} from "@/types.ts";
|
||||
import {onMounted, reactive, watch} from "vue";
|
||||
import {FormInstance, FormRules} from "element-plus";
|
||||
import {dictionaryResources} from "@/assets/dictionary.ts";
|
||||
import {cloneDeep} from "lodash-es";
|
||||
import {useRuntimeStore} from "@/stores/runtime.ts";
|
||||
import {FormInstance, FormRules} from "element-plus";
|
||||
import {emitter, EventKey} from "@/utils/eventBus.ts";
|
||||
import ChapterList from "@/components/ChapterList.vue";
|
||||
import {useBaseStore} from "@/stores/base.ts";
|
||||
|
||||
const baseStore = useBaseStore()
|
||||
let data = $ref({
|
||||
words: [],
|
||||
index: 0
|
||||
})
|
||||
|
||||
|
||||
const store = useBaseStore()
|
||||
const settingStore = useSettingStore()
|
||||
const runtimeStore = useRuntimeStore()
|
||||
|
||||
@@ -23,15 +31,21 @@ const emit = defineEmits([
|
||||
'close',
|
||||
])
|
||||
|
||||
let step = $ref(1)
|
||||
let step = $ref(0)
|
||||
let isEdit = $ref(true)
|
||||
|
||||
useDisableEventListener()
|
||||
|
||||
|
||||
let list = $computed(() => {
|
||||
return baseStore.myDicts.filter(v => v.type === DictType.customArticle).concat([{name: '',} as any])
|
||||
return store.myDicts.filter(v => v.type === DictType.customArticle)
|
||||
.concat([
|
||||
store.simple,
|
||||
store.wrong,
|
||||
store.collect
|
||||
])
|
||||
.concat([{name: '',} as any])
|
||||
})
|
||||
|
||||
let form = reactive({
|
||||
id: '',
|
||||
name: '123',
|
||||
@@ -42,6 +56,13 @@ let form = reactive({
|
||||
language: '',
|
||||
})
|
||||
|
||||
let wordForm = reactive({
|
||||
name: '',
|
||||
translate: '',
|
||||
usphone: '',
|
||||
ukphone: '',
|
||||
})
|
||||
|
||||
let languageCategoryList = []
|
||||
let categoryList = {}
|
||||
let tagList = {}
|
||||
@@ -107,16 +128,16 @@ async function onSubmit() {
|
||||
...form,
|
||||
}
|
||||
if (form.id) {
|
||||
let rIndex = baseStore.myDicts.findIndex(v => v.id === form.id)
|
||||
let rIndex = store.myDicts.findIndex(v => v.id === form.id)
|
||||
runtimeStore.editDict = data
|
||||
baseStore.myDicts[rIndex] = cloneDeep(data)
|
||||
store.myDicts[rIndex] = cloneDeep(data)
|
||||
isEdit = false
|
||||
} else {
|
||||
if (baseStore.myDicts.find(v => v.name === form.name)) {
|
||||
if (store.myDicts.find(v => v.name === form.name)) {
|
||||
return ElMessage.warning('已有相同名称词典!')
|
||||
} else {
|
||||
runtimeStore.editDict = data
|
||||
baseStore.myDicts.push(cloneDeep(data))
|
||||
store.myDicts.push(cloneDeep(data))
|
||||
isEdit = false
|
||||
console.log('submit!', data)
|
||||
}
|
||||
@@ -130,49 +151,44 @@ async function onSubmit() {
|
||||
function close() {
|
||||
emit('close')
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
:show-close="false"
|
||||
:header="false"
|
||||
@close="close"
|
||||
title="我的词典">
|
||||
<div class="slide">
|
||||
<div class="slide-list" :class="`step${step}`">
|
||||
<div class="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;isEdit = true"
|
||||
@selectDict="selectDict"
|
||||
:list="list"/>
|
||||
<div id="AddWordDialog">
|
||||
<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;isEdit = true"
|
||||
@selectDict="selectDict"
|
||||
:list="list"/>
|
||||
</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>
|
||||
</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>
|
||||
<Icon @click="close"
|
||||
class="hvr-grow pointer"
|
||||
width="20" color="#929596"
|
||||
icon="ion:close-outline"/>
|
||||
</header>
|
||||
<div class="detail" v-if="!isEdit">
|
||||
<div class="dict">
|
||||
</div>
|
||||
<Icon @click="close"
|
||||
class="hvr-grow pointer"
|
||||
width="20" color="#929596"
|
||||
icon="ion:close-outline"/>
|
||||
</header>
|
||||
<div class="detail" v-if="!isEdit">
|
||||
<div class="dict">
|
||||
<div class="info">
|
||||
<div class="name">{{ runtimeStore.editDict.name }}</div>
|
||||
<div class="desc">{{ runtimeStore.editDict.description }}</div>
|
||||
<div class="num">总文章:{{ runtimeStore.editDict.articles.length }}篇</div>
|
||||
@@ -185,86 +201,103 @@ function close() {
|
||||
:show-text="false"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="other">
|
||||
<div class="common-title">
|
||||
文章列表:共{{ runtimeStore.editDict.articles.length }}章
|
||||
<div class="add" v-if="false">
|
||||
<el-form
|
||||
ref="ruleFormRef"
|
||||
:rules="rules"
|
||||
:model="wordForm"
|
||||
label-width="60px">
|
||||
<el-form-item label="单词" prop="name">
|
||||
<el-input v-model="wordForm.name"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="翻译">
|
||||
<el-input v-model="wordForm.translate" type="textarea"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="音标">
|
||||
<el-input v-model="wordForm.ukphone"/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="step = 0">返回</el-button>
|
||||
<el-button type="primary" @click="onSubmit">确定</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list">
|
||||
<div class="list-header">
|
||||
<div class="name">{{ runtimeStore.editDict.name }}</div>
|
||||
<div class="flex-center gap10">
|
||||
<div class="name">{{ runtimeStore.editDict.words.length }}个单词</div>
|
||||
<Icon @click="emitter.emit(EventKey.openArticleListModal)"
|
||||
class="hvr-grow pointer"
|
||||
width="24" color="#929596"
|
||||
icon="mi:add"/>
|
||||
</div>
|
||||
<ChapterList
|
||||
:is-article="true"
|
||||
v-model:active-index="runtimeStore.editDict.chapterIndex"
|
||||
:dict="runtimeStore.editDict"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="edit" v-else>
|
||||
<el-form
|
||||
ref="ruleFormRef"
|
||||
:rules="rules"
|
||||
:model="form"
|
||||
label-width="120px">
|
||||
<el-form-item label="名称" prop="name">
|
||||
<el-input v-model="form.name"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="描述">
|
||||
<el-input v-model="form.description" type="textarea"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="分类" prop="languageCategory">
|
||||
<el-select
|
||||
v-model="form.languageCategory" placeholder="请选择选项">
|
||||
<el-option :label="i.name" :value="i.id" v-for="i in languageCategoryOptions"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="用途" prop="category">
|
||||
<el-select v-model="form.category" placeholder="请选择选项">
|
||||
<el-option :label="i" :value="i" v-for="i in categoryList[form.languageCategory]"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="标签" prop="tags">
|
||||
<el-select
|
||||
multiple
|
||||
v-model="form.tags" placeholder="请选择选项">
|
||||
<el-option :label="i" :value="i" v-for="i in tagList[form.category]"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="step = 0">返回</el-button>
|
||||
<el-button type="primary" @click="onSubmit">确定</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<WordList
|
||||
class="word-list"
|
||||
:is-active="true"
|
||||
@change="(i:number) => data.index = i"
|
||||
:list="runtimeStore.editDict.words"
|
||||
:activeIndex="data.index"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="edit" v-else>
|
||||
<el-form
|
||||
ref="ruleFormRef"
|
||||
:rules="rules"
|
||||
:model="form"
|
||||
label-width="120px">
|
||||
<el-form-item label="名称" prop="name">
|
||||
<el-input v-model="form.name"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="描述">
|
||||
<el-input v-model="form.description" type="textarea"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="分类" prop="languageCategory">
|
||||
<el-select
|
||||
v-model="form.languageCategory" placeholder="请选择选项">
|
||||
<el-option :label="i.name" :value="i.id" v-for="i in languageCategoryOptions"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="用途" prop="category">
|
||||
<el-select v-model="form.category" placeholder="请选择选项">
|
||||
<el-option :label="i" :value="i" v-for="i in categoryList[form.languageCategory]"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="标签" prop="tags">
|
||||
<el-select
|
||||
multiple
|
||||
v-model="form.tags" placeholder="请选择选项">
|
||||
<el-option :label="i" :value="i" v-for="i in tagList[form.category]"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="step = 0">返回</el-button>
|
||||
<el-button type="primary" @click="onSubmit">确定</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</Slide>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/assets/css/variable";
|
||||
|
||||
.slide {
|
||||
#AddWordDialog {
|
||||
position: fixed;
|
||||
width: 700rem;
|
||||
height: 75vh;
|
||||
height: 70vh;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate3D(-50%, -50%, 0);
|
||||
z-index: 9999999;
|
||||
background: var(--color-second-bg);
|
||||
|
||||
.slide-list {
|
||||
width: 200%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
transition: all .5s;
|
||||
}
|
||||
$header-height: 60rem;
|
||||
|
||||
.step1 {
|
||||
transform: translate3d(-50%, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
$header-height: 60rem;
|
||||
.page {
|
||||
padding: $space;
|
||||
padding-top: 0;
|
||||
width: 50%;
|
||||
|
||||
header {
|
||||
color: var(--color-font-3);
|
||||
@@ -280,62 +313,76 @@ $header-height: 60rem;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-page {
|
||||
color: black;
|
||||
.dict-list-page {
|
||||
padding: 0 $space;
|
||||
box-sizing: border-box;
|
||||
|
||||
.detail {
|
||||
display: flex;
|
||||
position: relative;
|
||||
gap: $space;
|
||||
}
|
||||
|
||||
.dict {
|
||||
overflow: auto;
|
||||
flex: 3;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10rem;
|
||||
padding: 15rem;
|
||||
min-height: 100rem;
|
||||
position: relative;
|
||||
border-radius: 10rem;
|
||||
background: var(--color-second-bg);
|
||||
color: var(--color-font-1);
|
||||
font-size: 14rem;
|
||||
.add-page {
|
||||
color: var(--color-font-1);
|
||||
//display: flex;
|
||||
//flex-direction: column;
|
||||
|
||||
.name {
|
||||
font-size: 28rem;
|
||||
margin-bottom: 10rem;
|
||||
}
|
||||
|
||||
.desc {
|
||||
font-size: 18rem;
|
||||
margin-bottom: 30rem;
|
||||
}
|
||||
|
||||
.count {
|
||||
cursor: pointer;
|
||||
border-bottom: 2px solid var(--color-item-active);
|
||||
}
|
||||
header {
|
||||
padding: 0 $space;
|
||||
}
|
||||
|
||||
.other {
|
||||
flex: 5;
|
||||
border-radius: 10rem;
|
||||
background: var(--color-second-bg);
|
||||
color: var(--color-font-1);
|
||||
padding: 10rem;
|
||||
.detail {
|
||||
flex: 1;
|
||||
height: calc(100% - $header-height);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
|
||||
.common-title {
|
||||
.dict {
|
||||
width: 50%;
|
||||
border-radius: 10rem;
|
||||
background: var(--color-second-bg);
|
||||
color: var(--color-font-1);
|
||||
padding-left: $space;
|
||||
box-sizing: border-box;
|
||||
|
||||
.info {
|
||||
border-radius: 8rem;
|
||||
background: gray;
|
||||
padding: 20rem;
|
||||
|
||||
.name {
|
||||
font-size: 28rem;
|
||||
margin-bottom: 10rem;
|
||||
}
|
||||
|
||||
.desc {
|
||||
font-size: 18rem;
|
||||
margin-bottom: 30rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.list {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
font-size: 14rem;
|
||||
padding-bottom: 20rem;
|
||||
|
||||
.list-header {
|
||||
min-height: 50rem;
|
||||
padding: 10rem 24rem;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 16rem;
|
||||
color: var(--color-font-3);
|
||||
|
||||
.name {
|
||||
font-size: 18rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import {$ref} from "vue/macros";
|
||||
import {Icon} from "@iconify/vue";
|
||||
import Close from "@/components/Close.vue";
|
||||
import Close from "@/components/icon/Close.vue";
|
||||
import {useWindowClick} from "@/hooks/event.ts";
|
||||
|
||||
defineProps<{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
import VolumeIcon from "@/components/VolumeIcon.vue";
|
||||
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
|
||||
|
||||
defineProps<{
|
||||
showVolume?: boolean,
|
||||
|
||||
196
src/components/Modal/AddWordDialog.vue
Normal file
196
src/components/Modal/AddWordDialog.vue
Normal file
@@ -0,0 +1,196 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import WordList from "@/components/WordList.vue";
|
||||
import {$computed, $ref} from "vue/macros";
|
||||
import Slide from "@/components/Slide.vue";
|
||||
import DictList from "@/components/DictList.vue";
|
||||
import {Icon} from "@iconify/vue";
|
||||
import {useBaseStore} from "@/stores/base.ts";
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import {useRuntimeStore} from "@/stores/runtime.ts";
|
||||
import {useDisableEventListener} from "@/hooks/event.ts";
|
||||
import {Dict, DictType, languageCategoryOptions, Sort} from "@/types.ts";
|
||||
import {onMounted, reactive, watch} from "vue";
|
||||
import {FormInstance, FormRules} from "element-plus";
|
||||
import {dictionaryResources} from "@/assets/dictionary.ts";
|
||||
import {cloneDeep} from "lodash-es";
|
||||
import {emitter, EventKey} from "@/utils/eventBus.ts";
|
||||
import ChapterList from "@/components/ChapterList.vue";
|
||||
|
||||
let data = $ref({
|
||||
words: [],
|
||||
index: 0
|
||||
})
|
||||
|
||||
|
||||
const store = useBaseStore()
|
||||
const settingStore = useSettingStore()
|
||||
const runtimeStore = useRuntimeStore()
|
||||
|
||||
const emit = defineEmits([
|
||||
'close',
|
||||
])
|
||||
|
||||
let step = $ref(0)
|
||||
let isEdit = $ref(true)
|
||||
|
||||
useDisableEventListener()
|
||||
|
||||
let list = $computed(() => {
|
||||
return store.myDicts.filter(v => v.type === DictType.customArticle)
|
||||
.concat([
|
||||
store.simple,
|
||||
store.wrong,
|
||||
store.collect
|
||||
])
|
||||
.concat([{name: '',} as any])
|
||||
})
|
||||
|
||||
let form = reactive({
|
||||
id: '',
|
||||
name: '123',
|
||||
description: '',
|
||||
category: '',
|
||||
tags: [],
|
||||
languageCategory: '',
|
||||
language: '',
|
||||
})
|
||||
|
||||
let wordForm = reactive({
|
||||
name: '',
|
||||
translate: '',
|
||||
usphone: '',
|
||||
ukphone: '',
|
||||
})
|
||||
|
||||
let languageCategoryList = []
|
||||
let categoryList = {}
|
||||
let tagList = {}
|
||||
const ruleFormRef = $ref<FormInstance>()
|
||||
const rules = reactive<FormRules>({
|
||||
name: [
|
||||
{required: true, message: '请输入名称', trigger: 'blur'},
|
||||
{max: 20, message: '名称不能超过20个字符', trigger: 'blur'},
|
||||
],
|
||||
category: [{required: true, message: '请选择', trigger: 'change'}],
|
||||
tags: [{required: true, message: '请选择', trigger: 'change'}],
|
||||
languageCategory: [{required: true, message: '请选择', trigger: 'change'}],
|
||||
})
|
||||
|
||||
watch(() => form.languageCategory, () => form.category = '')
|
||||
watch(() => form.category, () => form.tags = [])
|
||||
|
||||
onMounted(() => {
|
||||
dictionaryResources.map(v => {
|
||||
// if (!languageCategoryList.find(w => w === v.languageCategory)) {
|
||||
// languageCategoryList.push(v.languageCategory)
|
||||
// }
|
||||
if (categoryList[v.language]) {
|
||||
if (!categoryList[v.language].find(w => w === v.category)) {
|
||||
categoryList[v.language].push(v.category)
|
||||
}
|
||||
} else {
|
||||
categoryList[v.language] = [v.category]
|
||||
}
|
||||
if (tagList[v.category]) {
|
||||
tagList[v.category] = Array.from(new Set(tagList[v.category].concat(v.tags)))
|
||||
} else {
|
||||
tagList[v.category] = v.tags
|
||||
}
|
||||
})
|
||||
|
||||
console.log('languageCategoryList', languageCategoryList)
|
||||
console.log('categoryList', categoryList)
|
||||
console.log('tagList', tagList)
|
||||
})
|
||||
|
||||
function selectDict(dict: Dict) {
|
||||
runtimeStore.editDict = cloneDeep(dict)
|
||||
isEdit = false
|
||||
step = 1
|
||||
}
|
||||
|
||||
async function onSubmit() {
|
||||
await ruleFormRef.validate((valid, fields) => {
|
||||
if (valid) {
|
||||
let data = {
|
||||
sort: Sort.normal,
|
||||
type: DictType.customArticle,
|
||||
originWords: [],
|
||||
words: [],
|
||||
chapterWordNumber: 30,
|
||||
chapterWords: [],
|
||||
chapterIndex: 0,
|
||||
chapterWordIndex: 0,
|
||||
statistics: [],
|
||||
articles: [],
|
||||
url: '',
|
||||
...form,
|
||||
}
|
||||
if (form.id) {
|
||||
let rIndex = store.myDicts.findIndex(v => v.id === form.id)
|
||||
runtimeStore.editDict = data
|
||||
store.myDicts[rIndex] = cloneDeep(data)
|
||||
isEdit = false
|
||||
} else {
|
||||
if (store.myDicts.find(v => v.name === form.name)) {
|
||||
return ElMessage.warning('已有相同名称词典!')
|
||||
} else {
|
||||
runtimeStore.editDict = data
|
||||
store.myDicts.push(cloneDeep(data))
|
||||
isEdit = false
|
||||
console.log('submit!', data)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ElMessage.warning('请填写完整')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function close() {
|
||||
emit('close')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="AddWordDialog">
|
||||
<el-form
|
||||
ref="ruleFormRef"
|
||||
:rules="rules"
|
||||
:model="wordForm"
|
||||
label-width="60px">
|
||||
<el-form-item label="单词" prop="name">
|
||||
<el-input v-model="wordForm.name"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="翻译">
|
||||
<el-input v-model="wordForm.translate" type="textarea"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="音标">
|
||||
<el-input v-model="wordForm.ukphone"/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="step = 0">返回</el-button>
|
||||
<el-button type="primary" @click="onSubmit">确定</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/assets/css/variable";
|
||||
|
||||
#AddWordDialog {
|
||||
position: fixed;
|
||||
width: 700rem;
|
||||
height: 70vh;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate3D(-50%, -50%, 0);
|
||||
z-index: 9999999;
|
||||
background: var(--color-second-bg);
|
||||
|
||||
$header-height: 60rem;
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -8,7 +8,7 @@ import {Dict, DictType} from "@/types.ts"
|
||||
import PopConfirm from "@/components/PopConfirm.vue"
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import Close from "@/components/Close.vue";
|
||||
import Close from "@/components/icon/Close.vue";
|
||||
import Empty from "@/components/Empty.vue";
|
||||
import ArticleList from "@/components/Article/ArticleList.vue";
|
||||
import {useWordOptions} from "@/hooks/dict.ts";
|
||||
@@ -65,21 +65,23 @@ const {
|
||||
</div>
|
||||
<div class="slide-item">
|
||||
<div class="panel-page-item">
|
||||
<header>
|
||||
<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">
|
||||
<Tooltip title="添加">
|
||||
<IconWrapper>
|
||||
<Icon icon="fluent:add-12-regular"/>
|
||||
</IconWrapper>
|
||||
</Tooltip>
|
||||
<div class="dict-name" v-if="practiceType === DictType.word && store.collect.words.length">
|
||||
{{ store.collect.words.length }}个单词
|
||||
</div>
|
||||
<div class="dict-name" v-else> {{ store.collect.articles.length }}篇文章</div>
|
||||
<!-- <Tooltip title="添加">-->
|
||||
<!-- <IconWrapper>-->
|
||||
<!-- <Icon icon="fluent:add-12-regular"/>-->
|
||||
<!-- </IconWrapper>-->
|
||||
<!-- </Tooltip>-->
|
||||
<div class="dict-name" v-if="practiceType === DictType.article && store.collect.articles.length">
|
||||
{{ store.collect.articles.length }}篇文章
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="store.current.dictType !== DictType.collect &&
|
||||
(
|
||||
@@ -93,7 +95,7 @@ const {
|
||||
<BaseButton size="small">切换</BaseButton>
|
||||
</PopConfirm>
|
||||
</template>
|
||||
</header>
|
||||
</div>
|
||||
<template v-if="practiceType === DictType.word">
|
||||
<WordList
|
||||
v-if="store.collect.words.length"
|
||||
@@ -113,7 +115,7 @@ const {
|
||||
</div>
|
||||
<div class="slide-item">
|
||||
<div class="panel-page-item" v-if="store.simple.words.length">
|
||||
<header>
|
||||
<div class="list-header">
|
||||
<div class="dict-name">总词数:{{ store.simple.words.length }}</div>
|
||||
<template v-if="store.current.dictType !== DictType.simple && store.simple.words.length">
|
||||
<PopConfirm
|
||||
@@ -123,7 +125,7 @@ const {
|
||||
<BaseButton size="small">切换</BaseButton>
|
||||
</PopConfirm>
|
||||
</template>
|
||||
</header>
|
||||
</div>
|
||||
<WordList
|
||||
class="word-list"
|
||||
:show-del="true"
|
||||
@@ -134,7 +136,7 @@ const {
|
||||
</div>
|
||||
<div class="slide-item">
|
||||
<div class="panel-page-item" v-if="store.wrong.words.length">
|
||||
<header>
|
||||
<div class="list-header">
|
||||
<div class="dict-name">总词数:{{ store.wrong.words.length }}</div>
|
||||
<template
|
||||
v-if="store.current.dictType !== DictType.wrong && store.wrong.words.length">
|
||||
@@ -145,7 +147,7 @@ const {
|
||||
<BaseButton size="small">切换</BaseButton>
|
||||
</PopConfirm>
|
||||
</template>
|
||||
</header>
|
||||
</div>
|
||||
<WordList
|
||||
class="word-list"
|
||||
:show-del="true"
|
||||
|
||||
@@ -15,6 +15,9 @@ import PracticeArticle from "@/components/Practice/PracticeArticle/PracticeArtic
|
||||
import PracticeWord from "@/components/Practice/PracticeWord/PracticeWord.vue";
|
||||
import {ShortcutKey} from "@/types.ts";
|
||||
import useTheme from "@/hooks/useTheme.ts";
|
||||
import SettingModal from "@/components/Modal/SettingModal.vue";
|
||||
import DictModal from "@/components/Modal/DictModal.vue";
|
||||
import AddWordDialog from "@/components/Modal/AddWordDialog.vue";
|
||||
|
||||
const practiceStore = usePracticeStore()
|
||||
const store = useBaseStore()
|
||||
@@ -138,6 +141,9 @@ onUnmounted(() => {
|
||||
<PracticeWord ref="practiceRef" v-else/>
|
||||
<Footer/>
|
||||
</div>
|
||||
<AddWordDialog></AddWordDialog>
|
||||
<DictModal :model-value="runtimeStore.showDictModal" @close="runtimeStore.showDictModal = false"/>
|
||||
<SettingModal v-if="runtimeStore.showSettingModal" @close="runtimeStore.showSettingModal = false"/>
|
||||
<Statistics/>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -141,8 +141,6 @@ function onTyping(e: KeyboardEvent) {
|
||||
|
||||
emit('nextWord', currentWord)
|
||||
|
||||
|
||||
|
||||
if (!currentSentence.words[wordIndex]) {
|
||||
wordIndex = 0
|
||||
sentenceIndex++
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import {DefaultWord, Word} from "@/types.ts";
|
||||
import VolumeIcon from "@/components/VolumeIcon.vue";
|
||||
import {DefaultWord, ShortcutKey, Word} from "@/types.ts";
|
||||
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
|
||||
import {$computed, $ref} from "vue/macros";
|
||||
import {useBaseStore} from "@/stores/base.ts";
|
||||
import {usePracticeStore} from "@/stores/practice.ts";
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import {usePlayBeep, usePlayCorrect, usePlayKeyboardAudio, usePlayWordAudio} from "@/hooks/sound.ts";
|
||||
import {usePlayBeep, usePlayCorrect, usePlayKeyboardAudio, usePlayWordAudio, useTTsPlayAudio} from "@/hooks/sound.ts";
|
||||
import {emitter, EventKey} from "@/utils/eventBus.ts";
|
||||
import {cloneDeep} from "lodash-es";
|
||||
import {onUnmounted, watch, onMounted} from "vue";
|
||||
import Tooltip from "@/components/Tooltip.vue";
|
||||
|
||||
interface IProps {
|
||||
word: Word,
|
||||
@@ -35,7 +36,9 @@ const playBeep = usePlayBeep()
|
||||
const playCorrect = usePlayCorrect()
|
||||
const playKeyboardAudio = usePlayKeyboardAudio()
|
||||
const playWordAudio = usePlayWordAudio()
|
||||
const ttsPlayAudio = useTTsPlayAudio()
|
||||
const volumeIconRef: any = $ref()
|
||||
const volumeTranslateIconRef: any = $ref()
|
||||
|
||||
let displayWord = $computed(() => {
|
||||
return props.word.name.slice(input.length + wrong.length)
|
||||
@@ -157,7 +160,20 @@ defineExpose({del, showWord, hideWord, play})
|
||||
opacity: settingStore.translate ? 1 : 0
|
||||
}"
|
||||
>
|
||||
<div v-for="i in word.trans">{{ i }}</div>
|
||||
<div class="translate-item" v-for="(v,i) in word.trans">
|
||||
<span>{{ v }}</span>
|
||||
<!-- <div class="volumeIcon">-->
|
||||
<!-- <Tooltip-->
|
||||
<!-- v-if="i === word.trans.length - 1"-->
|
||||
<!-- :title="`发音(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.PlayTranslatePronunciation]})`"-->
|
||||
<!-- >-->
|
||||
<!-- <VolumeIcon-->
|
||||
<!-- ref="volumeTranslateIconRef"-->
|
||||
<!-- :simple="true"-->
|
||||
<!-- :cb="()=>ttsPlayAudio(word.trans.join(';'))"/>-->
|
||||
<!-- </Tooltip>-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
</div>
|
||||
<div class="word-wrapper">
|
||||
<div class="word"
|
||||
@@ -175,7 +191,11 @@ defineExpose({del, showWord, hideWord, play})
|
||||
</template>
|
||||
<span class="letter" v-else>{{ displayWord }}</span>
|
||||
</div>
|
||||
<VolumeIcon ref="volumeIconRef" :simple="true" :cb="()=>playWordAudio(word.name)"/>
|
||||
<Tooltip
|
||||
:title="`发音(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.PlayWordPronunciation]})`"
|
||||
>
|
||||
<VolumeIcon ref="volumeIconRef" :simple="true" :cb="() => playWordAudio(word.name)"/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div class="phonetic">{{ settingStore.wordSoundType === 'us' ? word.usphone : word.ukphone }}</div>
|
||||
</div>
|
||||
@@ -209,6 +229,23 @@ defineExpose({del, showWord, hideWord, play})
|
||||
transform: translateY(-50%);
|
||||
margin-bottom: 90rem;
|
||||
color: var(--color-font-2);
|
||||
|
||||
&:hover {
|
||||
.volumeIcon {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.translate-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10rem;
|
||||
}
|
||||
|
||||
.volumeIcon {
|
||||
transition: opacity .3s;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.word-wrapper {
|
||||
|
||||
@@ -206,7 +206,7 @@ onMounted(() => {
|
||||
emitter.on(ShortcutKey.Skip, skip)
|
||||
emitter.on(ShortcutKey.ToggleCollect, collect)
|
||||
emitter.on(ShortcutKey.ToggleSimple, toggleWordSimpleWrapper)
|
||||
emitter.on(ShortcutKey.PlaySound, play)
|
||||
emitter.on(ShortcutKey.PlayWordPronunciation, play)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
@@ -214,7 +214,7 @@ onUnmounted(() => {
|
||||
emitter.off(ShortcutKey.Skip, skip)
|
||||
emitter.off(ShortcutKey.ToggleCollect, collect)
|
||||
emitter.off(ShortcutKey.ToggleSimple, toggleWordSimpleWrapper)
|
||||
emitter.off(ShortcutKey.PlaySound, play)
|
||||
emitter.off(ShortcutKey.PlayWordPronunciation, play)
|
||||
})
|
||||
|
||||
</script>
|
||||
@@ -260,7 +260,7 @@ onUnmounted(() => {
|
||||
<Panel>
|
||||
<template v-slot="{active}">
|
||||
<div class="panel-page-item">
|
||||
<header>
|
||||
<div class="list-header">
|
||||
<div class="left">
|
||||
<Tooltip title="切换词典">
|
||||
<IconWrapper>
|
||||
@@ -281,7 +281,7 @@ onUnmounted(() => {
|
||||
<div class="right">
|
||||
{{ data.words.length }}个单词
|
||||
</div>
|
||||
</header>
|
||||
</div>
|
||||
<WordList
|
||||
class="word-list"
|
||||
:is-active="active"
|
||||
|
||||
48
src/components/Slide.vue
Normal file
48
src/components/Slide.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import {$computed} from "vue/macros";
|
||||
|
||||
const props = defineProps<{
|
||||
width?: string,
|
||||
height?: string,
|
||||
slideCount: number,
|
||||
step: number
|
||||
}>()
|
||||
|
||||
const style = $computed(() => {
|
||||
return {
|
||||
width: props.slideCount * 100 + '%',
|
||||
transform: `translate3d(-${100 / props.slideCount * props.step}%, 0, 0)`
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="slide">
|
||||
<div class="slide-list"
|
||||
:style="style">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.slide {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
.slide-list {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
transition: all .5s;
|
||||
}
|
||||
|
||||
:deep(.page) {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -25,7 +25,7 @@ function toggle() {
|
||||
/>
|
||||
</IconWrapper>
|
||||
</Tooltip>
|
||||
<EditBatchArticleModal/>
|
||||
<EditBatchArticleModal/>
|
||||
<AddDict v-if="show" @close="show = false"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import MiniModal from "@/components/MiniModal.vue";
|
||||
import MiniModal from "@/components/Modal/MiniModal.vue";
|
||||
import {Icon} from "@iconify/vue";
|
||||
import IconWrapper from "@/components/IconWrapper.vue";
|
||||
import Tooltip from "@/components/Tooltip.vue";
|
||||
|
||||
@@ -2,25 +2,20 @@
|
||||
import Tooltip from "@/components/Tooltip.vue"
|
||||
import useTheme from "@/hooks/useTheme.ts"
|
||||
import {useBaseStore} from "@/stores/base.ts"
|
||||
import SettingModal from "@/components/Toolbar/SettingModal.vue"
|
||||
import FeedbackModal from "@/components/Toolbar/FeedbackModal.vue"
|
||||
import DictModal from "@/components/Toolbar/DictModal.vue"
|
||||
|
||||
import {Icon} from '@iconify/vue';
|
||||
|
||||
import IconWrapper from "@/components/IconWrapper.vue";
|
||||
import {emitter, EventKey} from "@/utils/eventBus.ts"
|
||||
import {onMounted, watch} from "vue"
|
||||
import {watch} from "vue"
|
||||
import VolumeSetting from "@/components/Toolbar/VolumeSetting.vue";
|
||||
import RepeatSetting from "@/components/Toolbar/RepeatSetting.vue";
|
||||
import TranslateSetting from "@/components/Toolbar/TranslateSetting.vue";
|
||||
import Add from "@/components/Toolbar/Add.vue";
|
||||
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 BaseIcon from "@/components/BaseIcon.vue";
|
||||
|
||||
const {toggleTheme} = useTheme()
|
||||
const store = useBaseStore()
|
||||
@@ -147,8 +142,6 @@ watch(() => store.load, n => {
|
||||
color="#999"/>
|
||||
</Tooltip>
|
||||
</header>
|
||||
<DictModal :model-value="runtimeStore.showDictModal" @close="runtimeStore.showDictModal = false"/>
|
||||
<SettingModal v-if="runtimeStore.showSettingModal" @close="runtimeStore.showSettingModal = false"/>
|
||||
<FeedbackModal v-if="showFeedbackModal" @close="showFeedbackModal = false"/>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import MiniModal from "@/components/MiniModal.vue";
|
||||
import MiniModal from "@/components/Modal/MiniModal.vue";
|
||||
import {Icon} from "@iconify/vue";
|
||||
import IconWrapper from "@/components/IconWrapper.vue";
|
||||
import Tooltip from "@/components/Tooltip.vue";
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import MiniModal from "@/components/MiniModal.vue";
|
||||
import MiniModal from "@/components/Modal/MiniModal.vue";
|
||||
import {Icon} from "@iconify/vue";
|
||||
import IconWrapper from "@/components/IconWrapper.vue";
|
||||
import Tooltip from "@/components/Tooltip.vue";
|
||||
import {useWindowClick} from "@/hooks/event.ts";
|
||||
import {emitter, EventKey} from "@/utils/eventBus.ts";
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import VolumeIcon from "@/components/VolumeIcon.vue";
|
||||
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";
|
||||
|
||||
@@ -3,9 +3,8 @@ import {Word} from "../types";
|
||||
import {watch} from "vue"
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import ListItem from "@/components/ListItem.vue";
|
||||
import VolumeIcon from "@/components/VolumeIcon.vue";
|
||||
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
|
||||
import {usePlayWordAudio} from "@/hooks/sound.ts";
|
||||
import {useBaseStore} from "@/stores/base.ts";
|
||||
import {useWordOptions} from "@/hooks/dict.ts";
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
@@ -59,7 +58,6 @@ const {
|
||||
toggleWordCollect,
|
||||
isWordSimple,
|
||||
toggleWordSimple,
|
||||
delWrongWord
|
||||
} = useWordOptions()
|
||||
|
||||
</script>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import VolumeIcon from "@/components/VolumeIcon.vue";
|
||||
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
|
||||
import Modal from "@/components/Modal/Modal.vue";
|
||||
import {$ref} from "vue/macros";
|
||||
import {onMounted, onUnmounted, watch} from "vue";
|
||||
|
||||
@@ -53,15 +53,13 @@ defineExpose({play})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Tooltip v-if="props.simple"
|
||||
:title="`发音(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.PlaySound]})`"
|
||||
>
|
||||
<div class="center" @click.stop="click">
|
||||
<Icon v-if="step === 0" icon="bx:volume"/>
|
||||
<Icon v-if="step === 1" icon="bx:volume-low"/>
|
||||
<Icon v-if="step === 2" icon="bx:volume-full"/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<div class="center"
|
||||
v-if="props.simple"
|
||||
@click.stop="click">
|
||||
<Icon v-if="step === 0" icon="bx:volume"/>
|
||||
<Icon v-if="step === 1" icon="bx:volume-low"/>
|
||||
<Icon v-if="step === 2" icon="bx:volume-full"/>
|
||||
</div>
|
||||
<IconWrapper @click.stop="click" v-else>
|
||||
<div class="center">
|
||||
<Icon v-if="step === 0" icon="bx:volume"/>
|
||||
@@ -3,6 +3,7 @@ import {useSettingStore} from "@/stores/setting.ts";
|
||||
import {PronunciationApi} from "@/types.ts";
|
||||
import beep from "@/assets/sound/beep.wav";
|
||||
import correct from "@/assets/sound/correct.wav";
|
||||
import {$ref} from "vue/macros";
|
||||
|
||||
export function useSound(audioSrcList?: string[], audioFileLength?: number) {
|
||||
let audioList: HTMLAudioElement[] = $ref([])
|
||||
@@ -94,6 +95,30 @@ export function usePlayWordAudio() {
|
||||
return playAudio
|
||||
}
|
||||
|
||||
export function useTTsPlayAudio() {
|
||||
let isPlay = $ref(false)
|
||||
|
||||
function play(text: string) {
|
||||
// if (isPlay) {
|
||||
// isPlay = false
|
||||
// return window.speechSynthesis.pause();
|
||||
// }
|
||||
let msg = new SpeechSynthesisUtterance();
|
||||
msg.text = text
|
||||
msg.rate = 1;
|
||||
msg.pitch = 1;
|
||||
// msg.lang = 'en-US';
|
||||
msg.lang = 'zh-CN';
|
||||
isPlay = true
|
||||
window.speechSynthesis.speak(msg);
|
||||
console.log('text',text)
|
||||
|
||||
}
|
||||
|
||||
return play
|
||||
}
|
||||
|
||||
|
||||
export function usePlayAudio(url: string) {
|
||||
new Audio(url).play().then(r => void 0)
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ export const SaveDict = {
|
||||
}
|
||||
export const SaveConfig = {
|
||||
key: 'typing-word-config',
|
||||
version: 5
|
||||
version: 6
|
||||
}
|
||||
|
||||
export enum ShortcutKey {
|
||||
@@ -183,7 +183,8 @@ export enum ShortcutKey {
|
||||
NextChapter = 'NextChapter',
|
||||
RepeatChapter = 'RepeatChapter',
|
||||
DictationChapter = 'DictationChapter',
|
||||
PlaySound = 'PlaySound',
|
||||
PlayWordPronunciation = 'PlayWordPronunciation',
|
||||
// PlayTranslatePronunciation = 'PlayTranslatePronunciation',
|
||||
ToggleShowTranslate = 'ToggleShowTranslate',
|
||||
ToggleDictation = 'ToggleDictation',
|
||||
OpenSetting = 'OpenSetting',
|
||||
@@ -201,7 +202,8 @@ export const DefaultShortcutKeyMap = {
|
||||
[ShortcutKey.NextChapter]: 'Ctrl+➡',
|
||||
[ShortcutKey.RepeatChapter]: 'Ctrl+Enter',
|
||||
[ShortcutKey.DictationChapter]: 'Alt+Enter',
|
||||
[ShortcutKey.PlaySound]: 'Ctrl+P',
|
||||
[ShortcutKey.PlayWordPronunciation]: 'Ctrl+P',
|
||||
// [ShortcutKey.PlayTranslatePronunciation]: 'Ctrl+O',
|
||||
[ShortcutKey.ToggleShowTranslate]: 'Ctrl+Z',
|
||||
[ShortcutKey.ToggleDictation]: 'Ctrl+I',
|
||||
[ShortcutKey.OpenSetting]: 'Ctrl+S',
|
||||
|
||||
Reference in New Issue
Block a user