reconfiguration

This commit is contained in:
zyronon
2023-11-07 19:00:59 +08:00
parent 0cf09b8da3
commit 2d151d8cfe
27 changed files with 591 additions and 233 deletions

18
components.d.ts vendored
View File

@@ -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']

View File

@@ -38,7 +38,7 @@ watch(store.wrong.originWords, (n) => {
store.wrong.chapterWords = [store.wrong.words]
})
useStartKeyboardEventListener()
// useStartKeyboardEventListener()
onMounted(() => {
console.time()

View File

@@ -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;

View File

@@ -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>

View File

@@ -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<{

View File

@@ -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,

View 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>

View File

@@ -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"

View File

@@ -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>

View File

@@ -141,8 +141,6 @@ function onTyping(e: KeyboardEvent) {
emit('nextWord', currentWord)
if (!currentSentence.words[wordIndex]) {
wordIndex = 0
sentenceIndex++

View File

@@ -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 {

View File

@@ -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
View 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>

View File

@@ -25,7 +25,7 @@ function toggle() {
/>
</IconWrapper>
</Tooltip>
<EditBatchArticleModal/>
<EditBatchArticleModal/>
<AddDict v-if="show" @close="show = false"/>
</div>
</template>

View File

@@ -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";

View File

@@ -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>

View File

@@ -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";

View File

@@ -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";

View File

@@ -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>

View File

@@ -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";

View File

@@ -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"/>

View File

@@ -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)
}

View File

@@ -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',