feat:移除无用依赖

This commit is contained in:
zyronon
2025-08-03 22:02:00 +08:00
parent 6d9fbf234f
commit c372a18ca0
50 changed files with 540 additions and 1334 deletions

View File

@@ -1,11 +1,6 @@
//@import '/node_modules/element-plus/dist/index.css';
@use "anim" as *;
@use 'element-plus/theme-chalk/dark/css-vars' as *;
:root {
//修改element-ui的进度条底色
--el-border-color-lighter: #d1d1d1 !important;
--color-item-bg: rgb(228, 230, 232);
--color-item-hover: white;
//--color-item-active: rgb(75, 110, 175);
@@ -23,12 +18,12 @@
--practice-wrapper-translateX: 1px;
--article-width: 50vw;
--toolbar-width: 50rem;
--toolbar-height: 3.2rem;
--panel-width: 24rem;
--space: 1rem;
--stat-gap: 2rem;
--shadow: rgba(0, 0, 0, 0.08) 0px 4px 12px;
--panel-margin-left: calc(50% + var(--toolbar-width) / 2 + 1rem);
--article-panel-margin-left: calc(50% + var(--article-width) / 2 + 3.5rem);
--article-panel-margin-left: calc(50% + var(--article-width) / 2 + 1rem);
--anim-time: 0.3s;
--color-input-bg: white;
@@ -46,7 +41,7 @@
--zh-article-family: "Songti SC", "SimSun", "Noto Serif CJK SC", serif;
--btn-primary: rgb(75, 85, 99);
--btn-info: #909399;
--btn-info: white;
--color-primary: #E6E8EB;
--color-second: rgb(247, 247, 247);
@@ -54,7 +49,7 @@
--color-card-active: #FED7AA;
--color-list-item-active: rgb(253, 246, 236);
--color-icon-hightlight: #E6A23C;
--color-icon-hightlight: rgb(12, 140, 233);
//--color-icon-hightlight: rgb(12, 140, 233);
--color-sub-text: gray;
--color-main-text: rgb(91, 91, 91);
@@ -62,15 +57,26 @@
--color-select-text: white;
--color-notice-bg: rgb(247, 247, 247);
//修改element-ui的进度条底色
--el-border-color-lighter: #e2e5ed !important;
}
.footer {
&.hide {
--el-border-color-lighter: #dbdbdb !important;
}
}
html.dark {
--color-primary: #0E1217;
--color-second: rgb(30, 31, 34);
--color-third: rgb(43, 45, 48);
--color-card-active: rgb(84, 84, 84);
--color-list-item-active: rgb(84, 84, 84);
--color-icon-hightlight: #E6A23C;
--color-icon-hightlight: rgb(147, 173, 227);
--color-sub-text: #b8b8b8;
--color-main-text: rgba(249, 250, 251, 0.8);
--color-select-bg: rgb(147, 173, 227);
@@ -88,65 +94,51 @@ html.dark {
--color-font-3: rgba(255, 255, 255, 0.3);
--color-sub-gray: #383737;
--color-scrollbar: rgb(92, 93, 94);
--btn-info: transparent;
--color-input-bg: rgba(14, 18, 23, 1);
--color-input-icon: #383737;
--color-textarea-bg: rgb(43, 45, 48);
--color-article: white;
--el-border-color-lighter: var(--color-third) !important;
.footer {
&.hide {
--el-border-color-lighter: var(--color-third) !important;
}
}
}
@media (max-width: 1680px) {
:root {
--practice-wrapper-translateX: -12vw;
--toolbar-width: 40vw;
--article-width: 60vw;
--panel-width: 38rem;
--toolbar-height: 4.8rem;
--panel-margin-left: calc(50vw + var(--practice-wrapper-translateX) + var(--toolbar-width) / 2 + 5vw);
--article-panel-margin-left: calc(50% + var(--practice-wrapper-translateX) + var(--article-width) / 2 + 48rem);
--toolbar-width: 50vw;
--article-width: 50vw;
--panel-width: 20vw;
--space: 0.5rem;
}
.footer {
.bottom {
padding: 1.5rem 1rem 1rem 1rem !important;
}
.stat {
margin-top: 0.4rem !important;
.row {
gap: 0.5rem !important;
}
padding: .5rem !important;
}
}
}
@media (max-width: 1366px) {
:root {
--space: 1rem;
--practice-wrapper-translateX: -22vw;
--article-width: 53vw;
--panel-width: 30vw;
--panel-width: 20vw;
--article-width: 50vw;
--toolbar-width: 50vw;
--toolbar-height: 40rem;
--panel-margin-left: calc(50vw + var(--practice-wrapper-translateX) + var(--toolbar-width) / 2 + 14vw);
--article-panel-margin-left: calc(50% + var(--practice-wrapper-translateX) + var(--article-width) / 2 + 12vw);
--stat-gap: 0.5rem;
--space: 0.3rem;
}
.footer {
.bottom {
padding: 1.5rem 0.5rem 0.5rem 0.5rem !important;
}
.stat {
margin-top: 0.4rem !important;
.row {
gap: 0.5rem !important;
}
padding: .5rem !important;
}
}
}
@@ -397,7 +389,6 @@ footer {
}
}
.item-title {
display: flex;
align-items: center;

View File

@@ -30,7 +30,6 @@ defineEmits(['click'])
size,
type,
(disabled||loading) && 'disabled',
!disabled && 'hvr-grow'
]">
<span :style="{opacity:loading?0:1}"><slot></slot></span>
<Icon v-if="loading"
@@ -51,19 +50,25 @@ defineEmits(['click'])
.base-button {
cursor: pointer;
border-radius: .4rem;
padding: 0 1rem;
display: flex;
box-sizing: border-box;
display: inline-flex;
align-items: center;
justify-content: center;
transition: all .3s;
//background: #999;
//background: rgb(60, 63, 65);
//background: var(--color-second);
height: 2.5rem;
line-height: 1;
position: relative;
word-break: keep-all;
outline: none;
text-align: center;
transition: .1s;
user-select: none;
vertical-align: middle;
white-space: nowrap;
border-radius: .3rem;
padding: 0 0.9rem;
font-size: .9rem;
height: 2rem;
color: white;
& + .base-button {
margin-left: var(--space);
}
.loading {
position: absolute;
@@ -76,27 +81,21 @@ defineEmits(['click'])
}
&.small {
height: 2.4rem;
& > span {
font-size: .8rem;
}
border-radius: 0.2rem;
padding: 0 0.8rem;
height: 1.6rem;
font-size: .8rem;
}
&.large {
height: 3rem;
font-size: 1.1rem;
padding: 0 1.4rem;
& > span {
font-size: 1.1rem;
}
padding: 0 1.3rem;
height: 2.4rem;
font-size: 0.9rem;
}
& > span {
font-size: 1rem;
color: white;
line-height: 1;
transform: translateY(-5%);
:deep(a) {
color: white;
@@ -104,10 +103,9 @@ defineEmits(['click'])
}
&:hover {
opacity: .7;
opacity: .8;
}
&.primary {
background: var(--btn-primary);
}
@@ -117,12 +115,14 @@ defineEmits(['click'])
border-bottom: 2px solid transparent;
&:hover {
border-bottom: 2px solid var(--color-font-1);
border-bottom: 2px solid var(--color-font-2);
}
}
&.info {
background: var(--btn-info);
border: 1px solid var(--color-main-text);
color: var(--color-main-text);
}
&.active {

View File

@@ -43,7 +43,7 @@ $w: 1.4rem;
transition: all .3s;
&:hover:not(.disabled) {
background: var(--color-primary);
background: var(--color-icon-hightlight);
color: white;
}

2
src/global.d.ts vendored
View File

@@ -1,4 +1,4 @@
import {cloneDeep} from "lodash-es"
import {cloneDeep} from "@/utils"
export {}

View File

@@ -1,5 +1,5 @@
import {Article, ArticleWord, DictType, getDefaultArticleWord, Sentence} from "@/types.ts";
import {cloneDeep} from "lodash-es";
import {cloneDeep} from "@/utils";
import nlp from "compromise/one";
import {usePlayWordAudio} from "@/hooks/sound.ts";
import {getSentenceAllText, getSentenceAllTranslateText} from "@/hooks/translate.ts";

View File

@@ -1,7 +1,5 @@
import {Article, Dict, Word} from "@/types.ts";
import {Article, Word} from "@/types.ts";
import {useBaseStore} from "@/stores/base.ts";
import {cloneDeep,} from "lodash-es";
import {isArticle} from "@/hooks/article.ts";
export function useWordOptions() {

View File

@@ -1,6 +1,5 @@
import {Article, Sentence, TranslateEngine} from "@/types.ts";
import Baidu from "@opentranslate/baidu";
import {axiosInstance} from "@/utils/http.ts";
import {Translator} from "@opentranslate/translator/src/translator.ts";
export function getSentenceAllTranslateText(article: Article) {
@@ -27,7 +26,6 @@ export async function getNetworkTranslate(
let translator: Translator
if (translateEngine === TranslateEngine.Baidu) {
translator = new Baidu({
axios: axiosInstance as any,
config: {
appid: "20230910001811857",
key: "Xxe_yftQR3K3Ue43NQMC"

View File

@@ -3,28 +3,17 @@ import './assets/css/style.scss'
import 'virtual:uno.css';
import App from './App.vue'
import {createPinia} from "pinia"
import ZH from "@/locales/zh-CN.ts";
import {createI18n} from 'vue-i18n'
import router from "@/router.ts";
import VueVirtualScroller from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
import './global.d.ts'
const i18n = createI18n({
locale: 'zh-CN',
fallbackLocale: 'zh-CN',
messages: {
'zh-CN': ZH
},
})
const pinia = createPinia()
const app = createApp(App)
app.use(VueVirtualScroller)
// app.use(ElementPlus)
app.use(pinia)
app.use(i18n)
app.use(router)
app.directive('opacity', (el, binding) => {

View File

@@ -4,7 +4,7 @@ import {ref, watch} from "vue";
import {useSettingStore} from "@/stores/setting.ts";
import {getAudioFileUrl, useChangeAllSound, usePlayAudio, useWatchAllSound} from "@/hooks/sound.ts";
import {getShortcutKey, useDisableEventListener, useEventListener} from "@/hooks/event.ts";
import {cloneDeep} from "lodash-es";
import {cloneDeep} from "@/utils";
import {DefaultShortcutKeyMap, ShortcutKey} from "@/types.ts";
import BaseButton from "@/components/BaseButton.vue";
import {APP_NAME, EXPORT_DATA_KEY, SAVE_DICT_KEY, SAVE_SETTING_KEY, SoundFileOptions} from "@/utils/const.ts";
@@ -15,7 +15,7 @@ import {checkAndUpgradeSaveDict, checkAndUpgradeSaveSetting, shakeCommonDict} fr
import {GITHUB} from "@/config/ENV.ts";
import dayjs from "dayjs";
import BasePage from "@/pages/pc/components/BasePage.vue";
import {ElSwitch, ElSelect, ElOption, ElSlider, ElRadioGroup, ElRadio, ElInputNumber} from 'element-plus'
const emit = defineEmits<{
toggleDisabledDialogEscKey: [val: boolean]
@@ -162,11 +162,11 @@ function importData(e) {
<div class="row">
<label class="main-title">所有音效</label>
<div class="wrapper">
<el-switch v-model="settingStore.allSound"
@change="useChangeAllSound"
inline-prompt
active-text=""
inactive-text=""
<ElSwitch v-model="settingStore.allSound"
@change="useChangeAllSound"
inline-prompt
active-text=""
inactive-text=""
/>
</div>
</div>
@@ -174,35 +174,35 @@ function importData(e) {
<div class="row">
<label class="item-title">单词/句子自动发音</label>
<div class="wrapper">
<el-switch v-model="settingStore.wordSound"
inline-prompt
active-text=""
inactive-text=""
<ElSwitch v-model="settingStore.wordSound"
inline-prompt
active-text=""
inactive-text=""
/>
</div>
</div>
<div class="row">
<label class="sub-title">单词/句子发音口音</label>
<div class="wrapper">
<el-select v-model="settingStore.wordSoundType"
placeholder="请选择"
<ElSelect v-model="settingStore.wordSoundType"
placeholder="请选择"
>
<el-option label="美音" value="us"/>
<el-option label="英音" value="uk"/>
</el-select>
<ElOption label="美音" value="us"/>
<ElOption label="英音" value="uk"/>
</ElSelect>
</div>
</div>
<div class="row">
<label class="sub-title">音量</label>
<div class="wrapper">
<el-slider v-model="settingStore.wordSoundVolume"/>
<ElSlider v-model="settingStore.wordSoundVolume"/>
<span>{{ settingStore.wordSoundVolume }}%</span>
</div>
</div>
<div class="row">
<label class="sub-title">倍速</label>
<div class="wrapper">
<el-slider v-model="settingStore.wordSoundSpeed" :step="0.1" :min="0.5" :max="3"/>
<ElSlider v-model="settingStore.wordSoundSpeed" :step="0.1" :min="0.5" :max="3"/>
<span>{{ settingStore.wordSoundSpeed }}</span>
</div>
</div>
@@ -210,20 +210,20 @@ function importData(e) {
<div class="row">
<label class="item-title">按键音</label>
<div class="wrapper">
<el-switch v-model="settingStore.keyboardSound"
inline-prompt
active-text=""
inactive-text=""
<ElSwitch v-model="settingStore.keyboardSound"
inline-prompt
active-text=""
inactive-text=""
/>
</div>
</div>
<div class="row">
<label class="item-title">按键音效</label>
<div class="wrapper">
<el-select v-model="settingStore.keyboardSoundFile"
placeholder="请选择"
<ElSelect v-model="settingStore.keyboardSoundFile"
placeholder="请选择"
>
<el-option
<ElOption
v-for="item in SoundFileOptions"
:key="item.value"
:label="item.label"
@@ -235,14 +235,14 @@ function importData(e) {
:time="100"
@click="usePlayAudio(getAudioFileUrl(item.value)[0])"/>
</div>
</el-option>
</el-select>
</ElOption>
</ElSelect>
</div>
</div>
<div class="row">
<label class="sub-title">音量</label>
<div class="wrapper">
<el-slider v-model="settingStore.keyboardSoundVolume"/>
<ElSlider v-model="settingStore.keyboardSoundVolume"/>
<span>{{ settingStore.keyboardSoundVolume }}%</span>
</div>
</div>
@@ -250,17 +250,17 @@ function importData(e) {
<div class="row">
<label class="item-title">效果音输入错误完成时的音效</label>
<div class="wrapper">
<el-switch v-model="settingStore.effectSound"
inline-prompt
active-text=""
inactive-text=""
<ElSwitch v-model="settingStore.effectSound"
inline-prompt
active-text=""
inactive-text=""
/>
</div>
</div>
<div class="row">
<label class="sub-title">音量</label>
<div class="wrapper">
<el-slider v-model="settingStore.effectSoundVolume"/>
<ElSlider v-model="settingStore.effectSoundVolume"/>
<span>{{ settingStore.effectSoundVolume }}%</span>
</div>
</div>
@@ -269,19 +269,19 @@ function importData(e) {
<div class="row">
<label class="item-title">单词循环设置</label>
<div class="wrapper">
<el-radio-group v-model="settingStore.repeatCount">
<el-radio :value="1" size="default">1</el-radio>
<el-radio :value="2" size="default">2</el-radio>
<el-radio :value="3" size="default">3</el-radio>
<el-radio :value="5" size="default">5</el-radio>
<el-radio :value="100" size="default">自定义</el-radio>
</el-radio-group>
<ElRadioGroup v-model="settingStore.repeatCount">
<ElRadio :value="1" size="default">1</ElRadio>
<ElRadio :value="2" size="default">2</ElRadio>
<ElRadio :value="3" size="default">3</ElRadio>
<ElRadio :value="5" size="default">5</ElRadio>
<ElRadio :value="100" size="default">自定义</ElRadio>
</ElRadioGroup>
<div class="mini-row" v-if="settingStore.repeatCount === 100">
<label class="item-title">循环次数</label>
<el-input-number v-model="settingStore.repeatCustomCount"
:min="6"
:max="15"
type="number"
<ElInputNumber v-model="settingStore.repeatCustomCount"
:min="6"
:max="15"
type="number"
/>
</div>
</div>
@@ -289,10 +289,10 @@ function importData(e) {
<div class="row">
<label class="item-title">显示上一个/下一个单词</label>
<div class="wrapper">
<el-switch v-model="settingStore.showNearWord"
inline-prompt
active-text=""
inactive-text=""
<ElSwitch v-model="settingStore.showNearWord"
inline-prompt
active-text=""
inactive-text=""
/>
</div>
</div>
@@ -303,10 +303,10 @@ function importData(e) {
<div class="row">
<label class="item-title">忽略大小写</label>
<div class="wrapper">
<el-switch v-model="settingStore.ignoreCase"
inline-prompt
active-text=""
inactive-text=""
<ElSwitch v-model="settingStore.ignoreCase"
inline-prompt
active-text=""
inactive-text=""
/>
</div>
</div>
@@ -317,10 +317,10 @@ function importData(e) {
<div class="row">
<label class="item-title">允许默写模式下显示提示</label>
<div class="wrapper">
<el-switch v-model="settingStore.allowWordTip"
inline-prompt
active-text=""
inactive-text=""
<ElSwitch v-model="settingStore.allowWordTip"
inline-prompt
active-text=""
inactive-text=""
/>
</div>
</div>
@@ -334,7 +334,7 @@ function importData(e) {
<div class="row">
<label class="sub-title">外语字体</label>
<div class="wrapper">
<el-slider
<ElSlider
:min="10"
:max="100"
v-model="settingStore.fontSize.wordForeignFontSize"/>
@@ -344,7 +344,7 @@ function importData(e) {
<div class="row">
<label class="sub-title">中文字体</label>
<div class="wrapper">
<el-slider
<ElSlider
:min="10"
:max="100"
v-model="settingStore.fontSize.wordTranslateFontSize"/>
@@ -359,10 +359,10 @@ function importData(e) {
<div class="row">
<label class="sub-title">切换下一个单词时间</label>
<div class="wrapper">
<el-input-number v-model="settingStore.waitTimeForChangeWord"
:min="6"
:max="100"
type="number"
<ElInputNumber v-model="settingStore.waitTimeForChangeWord"
:min="6"
:max="100"
type="number"
/>
<span>毫秒</span>
</div>

View File

@@ -7,7 +7,7 @@ import {enArticle} from "@/assets/dictionary.ts";
import BasePage from "@/pages/pc/components/BasePage.vue";
import {useNav} from "@/utils";
import {Dict, DictResource, getDefaultDict} from "@/types.ts";
import {cloneDeep} from "lodash-es";
import {cloneDeep} from "@/utils";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {getArticleBookDataByUrl} from "@/utils/article.ts";
import BaseIcon from "@/components/BaseIcon.vue";
@@ -15,6 +15,7 @@ import Dialog from "@/pages/pc/components/dialog/Dialog.vue";
import Input from "@/pages/pc/components/Input.vue";
import {computed} from "vue";
import Book from "@/pages/pc/components/Book.vue";
import {ElProgress} from 'element-plus';
const {nav} = useNav()
const base = useBaseStore()
@@ -71,7 +72,7 @@ function startStudy() {
<BasePage>
<div class="card ">
<div class="flex justify-between items-center">
<div class="bg-slate-200 p-3 gap-4 rounded-md cursor-pointer flex items-center">
<div class="bg-third p-3 gap-4 rounded-md cursor-pointer flex items-center">
<span class="text-lg font-bold"
@click="getBookDetail2(base.currentBook)">{{
base.currentBook.name || '请选择书籍开始学习'
@@ -86,7 +87,7 @@ function startStudy() {
</div>
</div>
<div class="mt-5 text-sm">已学习{{ base.currentBook.lastLearnIndex }}篇文章</div>
<el-progress class="mt-1" :percentage="base.currentBookProgress" :show-text="false"></el-progress>
<ElProgress class="mt-1" :percentage="base.currentBookProgress" :show-text="false"></ElProgress>
</div>
<div class="card flex flex-col">

View File

@@ -2,7 +2,7 @@
import {onMounted, onUnmounted} from "vue";
import {Article, getDefaultArticle} from "@/types.ts";
import BaseButton from "@/components/BaseButton.vue";
import {cloneDeep} from "lodash-es";
import {cloneDeep} from "@/utils";
import {useBaseStore} from "@/stores/base.ts";
import List from "@/pages/pc/components/list/List.vue";

View File

@@ -11,7 +11,6 @@ import BaseButton from "@/components/BaseButton.vue";
import {useRoute, useRouter} from "vue-router";
import EditBook from "@/pages/pc/article/components/EditBook.vue";
import {computed, onMounted} from "vue";
import {cloneDeep} from "lodash-es";
const runtimeStore = useRuntimeStore()
const base = useBaseStore()
@@ -73,7 +72,7 @@ function formClose() {
<div class="flex justify-between items-center relative">
<BackIcon class="z-2" @click="$router.back"/>
<div class="absolute text-2xl text-align-center w-full">{{ runtimeStore.editDict.name }}</div>
<div class="flex gap-2">
<div class="flex">
<BaseButton type="info" @click="isEdit = true">编辑</BaseButton>
<BaseButton type="info" @click="router.push('batch-edit-article')">文章管理</BaseButton>
<BaseButton @click="addMyStudyList">学习</BaseButton>

View File

@@ -6,11 +6,10 @@ import EditAbleText from "@/pages/pc/components/EditAbleText.vue";
import {Icon} from "@iconify/vue";
import {getNetworkTranslate, getSentenceAllText, getSentenceAllTranslateText} from "@/hooks/translate.ts";
import {genArticleSectionData, splitCNArticle2, splitEnArticle2, usePlaySentenceAudio} from "@/hooks/article.ts";
import {cloneDeep, last} from "lodash-es";
import {_nextTick, _parseLRC, cloneDeep, last} from "@/utils";
import {watch} from "vue";
import Empty from "@/components/Empty.vue";
import {UploadProps} from "element-plus";
import {_nextTick, _parseLRC} from "@/utils";
import {ElInputNumber, ElOption, ElPopover, ElSelect, ElUpload, UploadProps} from "element-plus";
import * as Comparison from "string-comparison"
import BaseIcon from "@/components/BaseIcon.vue";
import Dialog from "@/pages/pc/components/dialog/Dialog.vue";
@@ -286,13 +285,13 @@ function setStartTime(val: Sentence, i: number, j: number) {
>
</textarea>
<div class="justify-end items-center flex">
<el-popover
<ElPopover
class="box-item"
title="使用方法"
placement="top"
:width="400"
>
<ol class="py-0 pl-5 my-0 text-base color-black/60">
<ol class="py-0 pl-5 my-0 text-base color-main">
<li>复制原文然后分句</li>
<li>点击 <span class="color-red font-bold">分句</span> 按钮进行自动分句<span
class="color-red font-bold"> </span> 手动编辑分句
@@ -304,9 +303,9 @@ function setStartTime(val: Sentence, i: number, j: number) {
<template #reference>
<Icon icon="ri:question-line" class="mr-3" width="20"/>
</template>
</el-popover>
<el-button type="primary" @click="splitText">分句</el-button>
<el-button type="primary" @click="() => apply()">应用</el-button>
</ElPopover>
<BaseButton @click="splitText">分句</BaseButton>
<BaseButton @click="apply()">应用</BaseButton>
</div>
</div>
<div class="row flex flex-col gap-2">
@@ -334,27 +333,23 @@ function setStartTime(val: Sentence, i: number, j: number) {
>
</textarea>
<div class="justify-between items-center flex">
<div class="flex gap-2 items-center ">
<el-button
type="primary"
@click="startNetworkTranslate"
:loading="progress!==0 && progress !== 100"
>翻译
</el-button>
<el-select v-model="networkTranslateEngine"
class="w-20"
<div class="flex gap-space items-center w-50 ">
<BaseButton @click="startNetworkTranslate"
:loading="progress!==0 && progress !== 100">翻译
</BaseButton>
<ElSelect v-model="networkTranslateEngine"
>
<el-option
<ElOption
v-for="item in TranslateEngineOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</ElSelect>
{{ progress }}%
</div>
<div class="flex items-center">
<el-popover
<ElPopover
class="box-item"
title="使用方法"
placement="top"
@@ -372,9 +367,9 @@ function setStartTime(val: Sentence, i: number, j: number) {
<template #reference>
<Icon icon="ri:question-line" class="mr-3" width="20"/>
</template>
</el-popover>
<el-button type="primary" @click="splitTranslateText">分句</el-button>
<el-button type="primary" @click="apply(true)">应用</el-button>
</ElPopover>
<BaseButton @click="splitTranslateText">分句</BaseButton>
<BaseButton @click="apply(true)">应用</BaseButton>
</div>
</div>
</div>
@@ -383,14 +378,14 @@ function setStartTime(val: Sentence, i: number, j: number) {
<div class="center">正文译文与结果均可编辑编辑后点击应用按钮会自动同步</div>
<div class="flex gap-2">
<BaseButton>添加音频</BaseButton>
<el-upload
<ElUpload
class="upload-demo"
:limit="1"
:on-change="handleChange"
:auto-upload="false"
>
<el-button type="primary">添加音频LRC文件</el-button>
</el-upload>
<BaseButton>添加音频LRC文件</BaseButton>
</ElUpload>
<audio ref="audioRef" :src="editArticle.audioSrc" controls></audio>
</div>
<template v-if="editArticle.sections.length">
@@ -498,11 +493,11 @@ function setStartTime(val: Sentence, i: number, j: number) {
<div>开始时间</div>
<div class="flex justify-between flex-1">
<div class="flex items-center gap-2">
<el-input-number v-model="editSentence.audioPosition[0]" :precision="2" :step="0.1">
<ElInputNumber v-model="editSentence.audioPosition[0]" :precision="2" :step="0.1">
<template #suffix>
<span>s</span>
</template>
</el-input-number>
</ElInputNumber>
<BaseIcon
@click="jumpAudio(editSentence.audioPosition[0])"
title="跳转"
@@ -521,11 +516,11 @@ function setStartTime(val: Sentence, i: number, j: number) {
<div>结束时间</div>
<div class="flex justify-between flex-1">
<div class="flex items-center gap-2">
<el-input-number v-model="editSentence.audioPosition[1]" :precision="2" :step="0.1">
<ElInputNumber v-model="editSentence.audioPosition[1]" :precision="2" :step="0.1">
<template #suffix>
<span>s</span>
</template>
</el-input-number>
</ElInputNumber>
<span></span>
<BaseButton size="small" @click="editSentence.audioPosition[1] = -1">结束</BaseButton>
</div>

View File

@@ -2,7 +2,7 @@
import {onMounted, onUnmounted} from "vue";
import {Article, getDefaultArticle} from "@/types.ts";
import BaseButton from "@/components/BaseButton.vue";
import {cloneDeep} from "lodash-es";
import {cloneDeep} from "@/utils";
import {useBaseStore} from "@/stores/base.ts";
import List from "@/pages/pc/components/list/List.vue";

View File

@@ -1,12 +1,13 @@
<script setup lang="ts">
import {Dict, DictType, getDefaultDict} from "@/types.ts";
import {cloneDeep} from "lodash-es";
import {cloneDeep} from "@/utils";
import {FormInstance, FormRules} from "element-plus";
import {ElForm,ElFormItem,ElInput,ElSelect,ElOption, FormInstance, FormRules} from "element-plus";
import {onMounted, reactive} from "vue";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {useBaseStore} from "@/stores/base.ts";
import BaseButton from "@/components/BaseButton.vue";
const props = defineProps<{
isAdd: boolean,
@@ -85,38 +86,38 @@ onMounted(() => {
<template>
<div class="w-120 mt-4">
<el-form
<ElForm
ref="dictFormRef"
:rules="dictRules"
:model="dictForm"
label-width="8rem">
<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="zh-CN"/>
<el-option label="英语" value="en"/>
<el-option label="德语" value="de"/>
<el-option label="日语" value="ja"/>
</el-select>
</el-form-item>
<ElFormItem label="名称" prop="name">
<ElInput v-model="dictForm.name"/>
</ElFormItem>
<ElFormItem label="描述">
<ElInput v-model="dictForm.description" type="textarea"/>
</ElFormItem>
<ElFormItem label="原文语言">
<ElSelect v-model="dictForm.language" placeholder="请选择选项">
<ElOption label="英语" value="en"/>
<ElOption label="德语" value="de"/>
<ElOption label="日语" value="ja"/>
<ElOption label="代码" value="code"/>
</ElSelect>
</ElFormItem>
<ElFormItem label="译文语言">
<ElSelect v-model="dictForm.translateLanguage" placeholder="请选择选项">
<ElOption label="中文" value="zh-CN"/>
<ElOption label="英语" value="en"/>
<ElOption label="德语" value="de"/>
<ElOption label="日语" value="ja"/>
</ElSelect>
</ElFormItem>
<div class="center">
<el-button @click="emit('close')">关闭</el-button>
<el-button type="primary" @click="onSubmit">确定</el-button>
<base-button type="info" @click="emit('close')">关闭</base-button>
<base-button type="primary" @click="onSubmit">确定</base-button>
</div>
</el-form>
</ElForm>
</div>
</template>

View File

@@ -33,8 +33,8 @@
</template>
<script setup>
import {ref, computed, watch, onMounted, nextTick} from 'vue'
import {shuffle} from "lodash-es";
import {computed, nextTick, onMounted, ref, watch} from 'vue'
import {shuffle} from "@/utils";
const props = defineProps({
stem: String,

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import TypingArticle from "./TypingArticle.vue";
import {Article, ArticleItem, ArticleWord, DisplayStatistics, getDefaultArticle, ShortcutKey, Word} from "@/types.ts";
import {cloneDeep} from "lodash-es";
import {cloneDeep} from "@/utils";
import Panel from "../../components/Panel.vue";
import {onMounted, onUnmounted} from "vue";
import {useBaseStore} from "@/stores/base.ts";
@@ -19,6 +19,7 @@ import ArticleList from "@/pages/pc/components/list/ArticleList.vue";
import {useOnKeyboardEventListener} from "@/hooks/event.ts";
import TranslateSetting from "@/pages/pc/components/toolbar/TranslateSetting.vue";
import {genArticleSectionData, usePlaySentenceAudio} from "@/hooks/article.ts";
import {ElProgress} from 'element-plus';
const store = useBaseStore()
const statisticsStore = usePracticeStore()
@@ -286,56 +287,52 @@ const {playSentenceAudio} = usePlaySentenceAudio()
:article="articleData.article"
/>
<Teleport to="body">
<div class="panel-wrapper">
<Panel v-if="tabIndex === 0">
<template v-slot="{active}">
<div class="panel-page-item">
<div class="list-header">
<div class="left">
<BaseIcon title="切换词典"
icon="carbon:change-catalog"/>
<div class="title">
{{ store.currentBook.name }}
</div>
<Tooltip
:title="`下一章(${settingStore.shortcutKeyMap[ShortcutKey.NextChapter]})`"
v-if="store.currentBook.lastLearnIndex < articleData.articles.length - 1">
<IconWrapper>
<Icon @click="emitter.emit(EventKey.continueStudy)" icon="octicon:arrow-right-24"/>
</IconWrapper>
</Tooltip>
</div>
<div class="right">
{{ articleData.articles.length }}篇文章
<div class="panel-wrapper">
<Panel>
<template v-slot="{active}">
<div class="panel-page-item pl-4">
<div class="list-header">
<div class="left">
<div class="title">
{{ store.currentBook.name }}
</div>
<Tooltip
:title="`下一篇(${settingStore.shortcutKeyMap[ShortcutKey.NextChapter]})`"
v-if="store.currentBook.lastLearnIndex < articleData.articles.length - 1">
<IconWrapper>
<Icon @click="emitter.emit(EventKey.continueStudy)" icon="octicon:arrow-right-24"/>
</IconWrapper>
</Tooltip>
</div>
<div class="right">
{{ articleData.articles.length }}篇文章
</div>
<ArticleList
:isActive="active"
:static="false"
:show-translate="settingStore.translate"
@click="handleChangeChapterIndex"
:active-id="articleData.article.id"
:list="articleData.articles ">
<template v-slot:suffix="{item,index}">
<BaseIcon
v-if="!isArticleCollect(item)"
class="collect"
@click="toggleArticleCollect(item)"
title="收藏" icon="ph:star"/>
<BaseIcon
v-else
class="fill"
@click="toggleArticleCollect(item)"
title="取消收藏" icon="ph:star-fill"/>
</template>
</ArticleList>
</div>
</template>
</Panel>
</div>
</Teleport>
<ArticleList
:isActive="active"
:static="false"
:show-translate="settingStore.translate"
@click="handleChangeChapterIndex"
:active-id="articleData.article.id"
:list="articleData.articles ">
<template v-slot:suffix="{item,index}">
<BaseIcon
v-if="!isArticleCollect(item)"
class="collect"
@click="toggleArticleCollect(item)"
title="收藏" icon="ph:star"/>
<BaseIcon
v-else
class="fill"
@click="toggleArticleCollect(item)"
title="取消收藏" icon="ph:star-fill"/>
</template>
</ArticleList>
</div>
</template>
</Panel>
</div>
<EditSingleArticleModal
v-model="showEditArticle"
@@ -345,34 +342,32 @@ const {playSentenceAudio} = usePlaySentenceAudio()
</div>
<div class="footer " :class="!settingStore.showToolbar && 'hide'">
<div class="bottom">
<div class="flex justify-between">
<div>
<el-progress
class="flex-1"
:percentage="progress"
:stroke-width="8"
:show-text="false"/>
<div class="stat">
<div class="row">
<div class="num">{{ speedMinute }}分钟</div>
<div class="line"></div>
<div class="name">时间</div>
</div>
<div class="row">
<div class="num">{{ statisticsStore.total }}</div>
<div class="line"></div>
<div class="name">单词总数</div>
</div>
<div class="row">
<div class="num">{{ format(statisticsStore.inputWordNumber, '', 0) }}</div>
<div class="line"></div>
<div class="name">输入数</div>
</div>
<div class="row">
<div class="num">{{ format(statisticsStore.wrong, '', 0) }}</div>
<div class="line"></div>
<div class="name">错误数</div>
</div>
<ElProgress
class="flex-1"
:percentage="progress"
:stroke-width="8"
:show-text="false"/>
<div class="flex justify-between items-center">
<div class="stat">
<div class="row">
<div class="num">{{ speedMinute }}分钟</div>
<div class="line"></div>
<div class="name">时间</div>
</div>
<div class="row">
<div class="num">{{ statisticsStore.total }}</div>
<div class="line"></div>
<div class="name">单词总数</div>
</div>
<div class="row">
<div class="num">{{ format(statisticsStore.inputWordNumber, '', 0) }}</div>
<div class="line"></div>
<div class="name">输入数</div>
</div>
<div class="row">
<div class="num">{{ format(statisticsStore.wrong, '', 0) }}</div>
<div class="line"></div>
<div class="name">错误数</div>
</div>
</div>
<div class="flex flex-col items-center justify-center gap-1">
@@ -417,7 +412,7 @@ const {playSentenceAudio} = usePlaySentenceAudio()
</div>
</div>
<div class="progress">
<el-progress :percentage="progress"
<ElProgress :percentage="progress"
:stroke-width="8"
:show-text="false"/>
</div>
@@ -471,12 +466,12 @@ const {playSentenceAudio} = usePlaySentenceAudio()
}
.panel-wrapper {
position: fixed;
left: 0;
top: .6rem;
position: absolute;
left: var(--article-panel-margin-left);
//left: 0;
top: .8rem;
z-index: 1;
margin-left: var(--article-panel-margin-left);
height: calc(100% - 1.2rem);
height: calc(100% - 1.5rem);
}
.footer {
@@ -501,7 +496,7 @@ const {playSentenceAudio} = usePlaySentenceAudio()
box-sizing: border-box;
border-radius: .6rem;
background: var(--color-second);
padding: .2rem var(--space) .4rem var(--space);
padding: .5rem var(--space);
z-index: 2;
border: 1px solid var(--color-item-border);
box-shadow: var(--shadow);
@@ -510,7 +505,7 @@ const {playSentenceAudio} = usePlaySentenceAudio()
margin-top: .5rem;
display: flex;
justify-content: space-around;
gap: 2rem;
gap: var(--stat-gap);
.row {
display: flex;
@@ -538,7 +533,7 @@ const {playSentenceAudio} = usePlaySentenceAudio()
bottom: 0;
}
:deep(.el-progress-bar__inner) {
:deep(.ElProgress-bar__inner) {
background: var(--color-scrollbar);
}

View File

@@ -5,11 +5,12 @@ import {Sort} from "@/types.ts";
import MiniDialog from "@/pages/pc/components/dialog/MiniDialog.vue";
import BaseIcon from "@/components/BaseIcon.vue";
import BaseButton from "@/components/BaseButton.vue";
import {cloneDeep, debounce, reverse, shuffle} from "lodash-es";
import {cloneDeep, debounce, reverse, shuffle} from "@/utils";
import Input from "@/pages/pc/components/Input.vue";
import PopConfirm from "@/pages/pc/components/PopConfirm.vue";
import Empty from "@/components/Empty.vue";
import {Icon} from "@iconify/vue";
import {ElCheckbox, ElPagination} from 'element-plus'
let list = defineModel('list')
@@ -117,7 +118,7 @@ const s = useSlots()
defineRender(
() => {
const d = (item) => <el-checkbox
const d = (item) => <ElCheckbox
modelValue={selectIds.includes(item.id)}
onChange={() => toggleSelect(item)}
size="large"/>
@@ -133,14 +134,14 @@ defineRender(
<Input
modelValue={searchKey}
onUpdate:modelValue=
{debounce(e => searchKey = e)}
{debounce(e => searchKey = e)}
class="flex-1"/>
<BaseButton onClick={() => (showSearchInput = false, searchKey = '')}>取消</BaseButton>
</div>
) : (
<div class="flex justify-between " v-else>
<div class="flex gap-2 items-center">
<el-checkbox
<ElCheckbox
disabled={!currentList.length}
onClick={() => toggleSelectAll()}
modelValue={selectAll}
@@ -219,14 +220,14 @@ defineRender(
})}
</div>
<div class="flex justify-end">
<el-pagination background
currentPage={pageNo}
onUpdate:current-page={handlePageNo}
pageSize={pageSize}
onUpdate:page-size={(e) => pageSize = e}
pageSizes={[20, 50, 100, 200]}
layout="prev, pager, next"
total={list.value.length}/>
<ElPagination background
currentPage={pageNo}
onUpdate:current-page={handlePageNo}
pageSize={pageSize}
onUpdate:page-size={(e) => pageSize = e}
pageSizes={[20, 50, 100, 200]}
layout="prev, pager, next"
total={list.value.length}/>
</div>
</>
) : <Empty/>

View File

@@ -1,6 +1,7 @@
<script setup lang="ts">
import {Dict} from "@/types.ts";
import {Icon} from "@iconify/vue";
import {ElProgress,ElCheckbox} from 'element-plus';
const props = defineProps<{
item?: Dict
@@ -36,11 +37,11 @@ const studyProgress = $computed(() => {
<div>{{ studyProgress }}{{ item?.length }}{{ quantifier }}</div>
</div>
<div class="absolute bottom-2 left-4 right-4">
<el-progress v-if="item?.lastLearnIndex || item.complete" class="mt-1"
<ElProgress v-if="item?.lastLearnIndex || item.complete" class="mt-1"
:percentage="progress"
:show-text="false"></el-progress>
:show-text="false"></ElProgress>
</div>
<el-checkbox v-if="showCheckbox"
<ElCheckbox v-if="showCheckbox"
:model-value="checked"
@click.stop="$emit('check')"
class="absolute left-3 bottom-2"/>

View File

@@ -1,6 +1,7 @@
<script setup lang="ts">
import BaseButton from "@/components/BaseButton.vue";
import {ElInput} from "element-plus";
import {watchEffect} from "vue";
@@ -37,7 +38,7 @@ function toggle() {
<div
v-if="edit"
class="edit-text">
<el-input
<ElInput
v-model="editVal"
ref="inputRef"
autosize

View File

@@ -20,7 +20,7 @@ $w: 1.4rem;
transition: all .3s;
&:hover {
background: var(--color-primary);
background: var(--color-icon-hightlight);
color: white;
}

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import {useBaseStore} from "@/stores/base.ts"
import {computed, onMounted, onUnmounted, provide, watch} from "vue"
import {computed, provide, watch} from "vue"
import {Dict, DictType, ShortcutKey} from "@/types.ts"
import PopConfirm from "@/pages/pc/components/PopConfirm.vue"
import BaseButton from "@/components/BaseButton.vue";
@@ -9,18 +9,14 @@ import {useSettingStore} from "@/stores/setting.ts";
import Close from "@/components/icon/Close.vue";
import Empty from "@/components/Empty.vue";
import {useArticleOptions, useWordOptions} from "@/hooks/dict.ts";
import {Icon} from "@iconify/vue";
import Tooltip from "@/pages/pc/components/Tooltip.vue";
import IconWrapper from "@/pages/pc/components/IconWrapper.vue";
import BaseIcon from "@/components/BaseIcon.vue";
import {emitter, EventKey, useEvent} from "@/utils/eventBus.ts";
import {useRouter} from "vue-router";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {cloneDeep} from "lodash-es";
import {useNav} from "@/utils";
import WordList from "@/pages/pc/components/list/WordList.vue";
import ArticleList from "@/pages/pc/components/list/ArticleList.vue";
import Slide from "@/pages/pc/components/Slide.vue";
import {useNav} from "@/utils";
const props = withDefaults(defineProps<{
type?: DictType

View File

@@ -1,644 +0,0 @@
<script setup lang="ts">
import {Icon} from '@iconify/vue';
import {ref, watch} from "vue";
import {useSettingStore} from "@/stores/setting.ts";
import {getAudioFileUrl, useChangeAllSound, usePlayAudio, useWatchAllSound} from "@/hooks/sound.ts";
import {getShortcutKey, useDisableEventListener, useEventListener} from "@/hooks/event.ts";
import {cloneDeep} from "lodash-es";
import {DefaultShortcutKeyMap, ShortcutKey} from "@/types.ts";
import BaseButton from "@/components/BaseButton.vue";
import {APP_NAME, EXPORT_DATA_KEY, SAVE_DICT_KEY, SAVE_SETTING_KEY, SoundFileOptions} from "@/utils/const.ts";
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
import {useBaseStore} from "@/stores/base.ts";
import {saveAs} from "file-saver";
import {checkAndUpgradeSaveDict, checkAndUpgradeSaveSetting, shakeCommonDict} from "@/utils";
import {GITHUB} from "@/config/ENV.ts";
import dayjs from "dayjs";
const emit = defineEmits<{
toggleDisabledDialogEscKey: [val: boolean]
}>()
const tabIndex = $ref(0)
const settingStore = useSettingStore()
const store = useBaseStore()
//@ts-ignore
const gitLastCommitHash = ref(LATEST_COMMIT_HASH);
useDisableEventListener(() => undefined)
useWatchAllSound()
let editShortcutKey = $ref('')
const disabledDefaultKeyboardEvent = $computed(() => {
return editShortcutKey && tabIndex === 2
})
watch(() => disabledDefaultKeyboardEvent, v => {
emit('toggleDisabledDialogEscKey', !!v)
})
useEventListener('keydown', (e: KeyboardEvent) => {
if (!disabledDefaultKeyboardEvent) return
e.preventDefault()
let shortcutKey = getShortcutKey(e)
// console.log('e', e, e.keyCode, e.ctrlKey, e.altKey, e.shiftKey)
// console.log('key', shortcutKey)
// if (shortcutKey[shortcutKey.length-1] === '+') {
// settingStore.shortcutKeyMap[editShortcutKey] = DefaultShortcutKeyMap[editShortcutKey]
// return ElMessage.warning('设备失败!')
// }
if (editShortcutKey) {
if (shortcutKey === 'Delete') {
settingStore.shortcutKeyMap[editShortcutKey] = ''
} else {
for (const [k, v] of Object.entries(settingStore.shortcutKeyMap)) {
if (v === shortcutKey && k !== editShortcutKey) {
settingStore.shortcutKeyMap[editShortcutKey] = DefaultShortcutKeyMap[editShortcutKey]
return ElMessage.warning('快捷键重复!')
}
}
settingStore.shortcutKeyMap[editShortcutKey] = shortcutKey
}
}
})
function resetShortcutKeyMap() {
editShortcutKey = ''
settingStore.shortcutKeyMap = cloneDeep(DefaultShortcutKeyMap)
ElMessage.success('恢复成功')
}
function exportData() {
let data = {
version: EXPORT_DATA_KEY.version,
val: {
setting: {
version: SAVE_SETTING_KEY.version,
val: settingStore.$state
},
dict: {
version: SAVE_DICT_KEY.version,
val: shakeCommonDict(store.$state)
}
}
}
let blob = new Blob([JSON.stringify(data)], {type: "text/plain;charset=utf-8"});
saveAs(blob, `${APP_NAME}-User-Data-${dayjs().format('YYYY-MM-DD HH-mm-ss')}.json`);
ElMessage.success('导出成功!')
}
function importData(e) {
let file = e.target.files[0]
if (!file) return
// no()
let reader = new FileReader();
reader.onload = function (v) {
let str: any = v.target.result;
if (str) {
let obj = {
version: -1,
val: {
setting: {},
dict: {},
}
}
try {
obj = JSON.parse(str)
} catch (err) {
ElMessage.error('导入失败!')
}
if (obj.version === EXPORT_DATA_KEY.version) {
} else {
//TODO
}
let data = obj.val
let settingState = checkAndUpgradeSaveSetting(data.setting)
settingStore.setState(settingState)
let dictState = checkAndUpgradeSaveDict(data.dict)
store.init(dictState)
ElMessage.success('导入成功!')
}
}
reader.readAsText(file);
}
</script>
<template>
<div class="setting">
<div class="left">
<div class="tabs">
<div class="tab" :class="tabIndex === 0 && 'active'" @click="tabIndex = 0">
<Icon icon="bx:headphone" width="20" color="#0C8CE9"/>
<span>音效设置</span>
</div>
<div class="tab" :class="tabIndex === 2 && 'active'" @click="tabIndex = 2">
<Icon icon="material-symbols:keyboard-outline" width="20" color="#0C8CE9"/>
<span>快捷键设置</span>
</div>
<div class="tab" :class="tabIndex === 1 && 'active'" @click="tabIndex = 1">
<Icon icon="icon-park-outline:setting-config" width="20" color="#0C8CE9"/>
<span>其他设置</span>
</div>
<div class="tab" :class="tabIndex === 3 && 'active'" @click="tabIndex = 3">
<Icon icon="mdi:database-cog-outline" width="20" color="#0C8CE9"/>
<span>数据管理</span>
</div>
<div class="tab" :class="tabIndex === 4 && 'active'" @click="tabIndex = 4">
<Icon icon="mingcute:service-fill" width="20" color="#0C8CE9"/>
<span>反馈</span>
</div>
<div class="tab" :class="tabIndex === 5 && 'active'" @click="tabIndex = 5">
<Icon icon="mdi:about-circle-outline" width="20" color="#0C8CE9"/>
<span>关于</span>
</div>
</div>
<div class="git-log">
Build {{ gitLastCommitHash }}
</div>
</div>
<div class="content">
<div v-if="tabIndex === 0">
<div class="row">
<label class="main-title">所有音效</label>
<div class="wrapper">
<el-switch v-model="settingStore.allSound"
@change="useChangeAllSound"
inline-prompt
active-text=""
inactive-text=""
/>
</div>
</div>
<div class="line"></div>
<div class="row">
<label class="item-title">单词/句子自动发音</label>
<div class="wrapper">
<el-switch v-model="settingStore.wordSound"
inline-prompt
active-text=""
inactive-text=""
/>
</div>
</div>
<div class="row">
<label class="sub-title">单词/句子发音口音</label>
<div class="wrapper">
<el-select v-model="settingStore.wordSoundType"
placeholder="请选择"
>
<el-option label="美音" value="us"/>
<el-option label="英音" value="uk"/>
</el-select>
</div>
</div>
<div class="row">
<label class="sub-title">音量</label>
<div class="wrapper">
<el-slider v-model="settingStore.wordSoundVolume"/>
<span>{{ settingStore.wordSoundVolume }}%</span>
</div>
</div>
<div class="row">
<label class="sub-title">倍速</label>
<div class="wrapper">
<el-slider v-model="settingStore.wordSoundSpeed" :step="0.1" :min="0.5" :max="3"/>
<span>{{ settingStore.wordSoundSpeed }}</span>
</div>
</div>
<div class="line"></div>
<div class="row">
<label class="item-title">按键音</label>
<div class="wrapper">
<el-switch v-model="settingStore.keyboardSound"
inline-prompt
active-text=""
inactive-text=""
/>
</div>
</div>
<div class="row">
<label class="item-title">按键音效</label>
<div class="wrapper">
<el-select v-model="settingStore.keyboardSoundFile"
placeholder="请选择"
>
<el-option
v-for="item in SoundFileOptions"
:key="item.value"
:label="item.label"
:value="item.value"
>
<div class="el-option-row">
<span>{{ item.label }}</span>
<VolumeIcon
:time="100"
@click="usePlayAudio(getAudioFileUrl(item.value)[0])"/>
</div>
</el-option>
</el-select>
</div>
</div>
<div class="row">
<label class="sub-title">音量</label>
<div class="wrapper">
<el-slider v-model="settingStore.keyboardSoundVolume"/>
<span>{{ settingStore.keyboardSoundVolume }}%</span>
</div>
</div>
<div class="line"></div>
<div class="row">
<label class="item-title">效果音输入错误完成时的音效</label>
<div class="wrapper">
<el-switch v-model="settingStore.effectSound"
inline-prompt
active-text=""
inactive-text=""
/>
</div>
</div>
<div class="row">
<label class="sub-title">音量</label>
<div class="wrapper">
<el-slider v-model="settingStore.effectSoundVolume"/>
<span>{{ settingStore.effectSoundVolume }}%</span>
</div>
</div>
</div>
<div v-if="tabIndex === 1">
<div class="row">
<label class="item-title">显示上一个/下一个单词</label>
<div class="wrapper">
<el-switch v-model="settingStore.showNearWord"
inline-prompt
active-text=""
inactive-text=""
/>
</div>
</div>
<div class="desc">
开启后练习中会在上方显示上一个/下一个单词
</div>
<div class="line"></div>
<div class="row">
<label class="item-title">忽略大小写</label>
<div class="wrapper">
<el-switch v-model="settingStore.ignoreCase"
inline-prompt
active-text=""
inactive-text=""
/>
</div>
</div>
<div class="desc">
开启后输入时不区分大小写如输入helloHello都会被认为是正确的
</div>
<div class="line"></div>
<div class="row">
<label class="item-title">允许默写模式下显示提示</label>
<div class="wrapper">
<el-switch v-model="settingStore.allowWordTip"
inline-prompt
active-text=""
inactive-text=""
/>
</div>
</div>
<div class="desc">
开启后可以通过鼠标 hover 单词或者按 {{ settingStore.shortcutKeyMap[ShortcutKey.ShowWord] }} 显示正确答案
</div>
<div class="line"></div>
<div class="row">
<label class="item-title">字体设置(仅可调整单词练习)</label>
</div>
<div class="row">
<label class="sub-title">外语字体</label>
<div class="wrapper">
<el-slider
:min="10"
:max="100"
v-model="settingStore.fontSize.wordForeignFontSize"/>
<span>{{ settingStore.fontSize.wordForeignFontSize }}</span>
</div>
</div>
<div class="row">
<label class="sub-title">中文字体</label>
<div class="wrapper">
<el-slider
:min="10"
:max="100"
v-model="settingStore.fontSize.wordTranslateFontSize"/>
<span>{{ settingStore.fontSize.wordTranslateFontSize }}</span>
</div>
</div>
<div class="line"></div>
<div class="row">
<label class="item-title">其他设置</label>
</div>
<div class="row">
<label class="sub-title">切换下一个单词时间</label>
<div class="wrapper">
<el-input-number v-model="settingStore.waitTimeForChangeWord"
:min="6"
:max="100"
type="number"
/>
<span>毫秒</span>
</div>
</div>
</div>
<div class="body" v-if="tabIndex === 2">
<div class="row">
<label class="main-title">功能</label>
<div class="wrapper">快捷键(点击可修改)</div>
</div>
<div class="scroll">
<div class="row" v-for="item of Object.entries(settingStore.shortcutKeyMap)">
<label class="item-title">{{ $t(item[0]) }}</label>
<div class="wrapper" @click="editShortcutKey = item[0]">
<div class="set-key" v-if="editShortcutKey === item[0]">
<input :value="item[1]?item[1]:'未设置快捷键'" readonly type="text" @blur="editShortcutKey = ''">
<span @click.stop="editShortcutKey = ''">直接按键盘进行设置</span>
</div>
<div v-else>
<div v-if="item[1]">{{ item[1] }}</div>
<span v-else>未设置快捷键</span>
</div>
</div>
</div>
</div>
<div class="row footer">
<label class="item-title"></label>
<div class="wrapper">
<BaseButton @click="resetShortcutKeyMap">恢复默认</BaseButton>
</div>
</div>
</div>
<div v-if="tabIndex === 3">
<div class="row">
<div class="main-title">数据导出</div>
</div>
<div class="row">
<label class="sub-title">
目前用户的所有数据(自定义设置自定义词典练习进度等)
<b>仅保存在本地</b>
如果您需要在不同的设备浏览器或者其他非官方部署上使用 {{ APP_NAME }} 您需要手动进行数据同步和保存
</label>
</div>
<div class="row">
<BaseButton @click="exportData">数据导出</BaseButton>
</div>
<div class="row">
<div class="main-title">数据导入</div>
</div>
<div class="row">
<label class="sub-title">
请注意导入数据将
<b style="color: red"> 完全覆盖 </b>
当前数据请谨慎操作
</label>
</div>
<div class="row">
<div class="import hvr-grow">
<BaseButton>数据导入</BaseButton>
<input type="file"
accept="application/json"
@change="importData">
</div>
</div>
</div>
<div v-if="tabIndex === 4" class="feedback-modal">
<div>
给我发Email<a href="mailto:zyronon@163.com">zyronon@163.com</a>
</div>
<p>or</p>
<div class="github">
<span><a :href="GITHUB" target="_blank">Github</a>上给我提一个
<a :href="`${GITHUB}/issues`" target="_blank">Issue</a>
</span>
<div class="options">
<BaseButton>
<a :href="`${GITHUB}/issues/new?assignees=&labels=&projects=&template=%E5%8D%95%E8%AF%8D%E9%94%99%E8%AF%AF---word-error.md&title=%E5%8D%95%E8%AF%8D%E9%94%99%E8%AF%AF+%7C+Word+error`"
target="_blank">词典错误</a>
</BaseButton>
<BaseButton>
<a :href="`${GITHUB}/issues/new?assignees=&labels=&projects=&template=问题报告---bug-report-.md&title=问题报告+%7C+Bug+report+`"
target="_blank">反馈BUG</a>
</BaseButton>
<BaseButton>
<a :href="`${GITHUB}/issues/new?assignees=&labels=&projects=&template=功能请求---feature-request.md&title=功能请求+%7C+Feature+request`"
target="_blank">功能请求</a>
</BaseButton>
</div>
</div>
</div>
<div v-if="tabIndex === 5" class="about">
<h1>Type Words</h1>
<p>
本项目完全开源好用请大家多多点Star
</p>
<p>
GitHub地址<a href="https://github.com/zyronon/typing-word">https://github.com/zyronon/typing-word</a>
</p>
<p>
反馈<a href="https://github.com/zyronon/typing-word/issues">https://github.com/zyronon/typing-word/issues</a>
</p>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.setting {
width: 40vw;
height: 70vh;
display: flex;
color: var(--color-font-1);
.left {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
.tabs {
padding: .6rem 1.6rem;
display: flex;
flex-direction: column;
//align-items: center;
//justify-content: center;
gap: .6rem;
.tab {
cursor: pointer;
padding: .6rem .9rem;
border-radius: .5rem;
display: flex;
align-items: center;
gap: .6rem;
&.active {
background: var(--color-item-bg);
}
}
}
.git-log {
font-size: .6rem;
color: gray;
margin-bottom: .3rem;
}
}
.content {
flex: 1;
height: 100%;
overflow: auto;
padding: 0 var(--space);
.row {
min-height: 2.6rem;
display: flex;
justify-content: space-between;
align-items: center;
gap: calc(var(--space) * 5);
.wrapper {
height: 2rem;
flex: 1;
display: flex;
justify-content: flex-end;
gap: var(--space);
span {
text-align: right;
//width: 30rem;
font-size: .7rem;
color: gray;
}
.set-key {
align-items: center;
input {
width: 9rem;
box-sizing: border-box;
margin-right: .6rem;
height: 1.8rem;
outline: none;
font-size: 1rem;
border: 1px solid gray;
border-radius: .2rem;
padding: 0 .3rem;
background: var(--color-second);
color: var(--color-font-1);
}
}
}
.main-title {
font-size: 1.1rem;
font-weight: bold;
}
.item-title {
font-size: 1rem;
}
.sub-title {
font-size: .9rem;
}
}
.body {
height: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
}
.scroll {
flex: 1;
padding-right: .6rem;
overflow: auto;
}
.footer {
margin-bottom: 1.3rem;
}
.desc {
margin-bottom: .6rem;
font-size: .8rem;
}
.line {
border-bottom: 1px solid #c4c3c3;
}
}
}
.el-option-row {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
.icon-wrapper {
transform: translateX(10rem);
}
}
.import {
display: inline-flex;
position: relative;
input {
position: absolute;
height: 100%;
width: 100%;
opacity: 0;
}
}
.feedback-modal {
//height: 80vh;
display: flex;
flex-direction: column;
align-items: center;
padding: var(--space);
//justify-content: center;
color: var(--color-font-1);
p {
font-size: 2.4rem;
}
.github {
display: flex;
align-items: center;
gap: var(--space);
.options {
display: flex;
flex-direction: column;
gap: .6rem;
}
}
}
.about {
text-align: center;
}
</style>

View File

@@ -3,6 +3,7 @@
import {Word} from "@/types.ts";
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
import {usePlayWordAudio} from "@/hooks/sound.ts";
import {ElPopover} from 'element-plus'
const props = withDefaults(defineProps<{
item: Word,
@@ -35,7 +36,7 @@ const playWordAudio = usePlayWordAudio()
</div>
<div class="item-sub-title flex flex-col gap-2" v-if="item.trans.length && showTranslate">
<div v-for="v in item.trans">
<el-popover
<ElPopover
v-if="v.cn.length > 30 && showTransPop"
width="300"
:content="v.pos + ' ' + v.cn"
@@ -44,7 +45,7 @@ const playWordAudio = usePlayWordAudio()
<template #reference>
<span>{{ v.pos + ' ' + v.cn.slice(0, 30) + '...' }}</span>
</template>
</el-popover>
</ElPopover>
<span v-else>{{ v.pos + ' ' + v.cn }}</span>
</div>
</div>

View File

@@ -2,7 +2,7 @@
import BaseIcon from "@/components/BaseIcon.vue";
import Input from "@/pages/pc/components/Input.vue";
import {cloneDeep, throttle} from "lodash-es";
import {cloneDeep, throttle} from "@/utils";
import {Article} from "@/types.ts";
interface IProps {

View File

@@ -4,6 +4,7 @@ import {Word} from "@/types.ts";
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
import BaseList from "@/pages/pc/components/list/BaseList.vue";
import {usePlayWordAudio} from "@/hooks/sound.ts";
import {ElPopover} from 'element-plus'
const props = withDefaults(defineProps<{
list: Word[],
@@ -53,7 +54,7 @@ defineExpose({scrollToBottom, scrollToItem})
</div>
<div class="item-sub-title flex flex-col gap-2" v-if="item.trans.length && showTranslate">
<div v-for="v in item.trans">
<el-popover
<ElPopover
v-if="v.cn.length > 30"
width="300"
:content="v.pos + ' ' + v.cn"
@@ -62,7 +63,7 @@ defineExpose({scrollToBottom, scrollToItem})
<template #reference>
<span>{{ v.pos + ' ' + v.cn.slice(0, 30) + '...' }}</span>
</template>
</el-popover>
</ElPopover>
<span v-else>{{ v.pos + ' ' + v.cn }}</span>
</div>
</div>

View File

@@ -6,11 +6,10 @@ import IconWrapper from "@/pages/pc/components/IconWrapper.vue";
import Tooltip from "@/pages/pc/components/Tooltip.vue";
import {useBaseStore} from "@/stores/base.ts";
import {useWindowClick} from "@/hooks/event.ts";
import {emitter, EventKey} from "@/utils/eventBus.ts";
import BaseButton from "@/components/BaseButton.vue";
import Dialog from "@/pages/pc/components/dialog/Dialog.vue";
import {useSettingStore} from "@/stores/setting.ts";
import {ShortcutKey} from "@/types.ts";
import {ElSwitch, ElRadioGroup,ElRadioButton,ElSelect,ElOption} from 'element-plus'
const store = useBaseStore()
const settingStore = useSettingStore()
@@ -59,19 +58,19 @@ function save() {
<div class="mini-row">
<label class="item-title">显示翻译</label>
<div class="wrapper">
<el-switch v-model="settingStore.translate"
inline-prompt
active-text=""
inactive-text=""
<ElSwitch v-model="settingStore.translate"
inline-prompt
active-text=""
inactive-text=""
/>
</div>
</div>
<div class="mini-row">
<label class="item-title">翻译类型</label>
<el-radio-group v-model="translateType" size="small">
<el-radio-button :value="1">本地翻译</el-radio-button>
<el-radio-button :value="0">网络翻译</el-radio-button>
</el-radio-group>
<ElRadioGroup v-model="translateType" size="small">
<ElRadioButton :value="1">本地翻译</ElRadioButton>
<ElRadioButton :value="0">网络翻译</ElRadioButton>
</ElRadioGroup>
</div>
<div class="mini-row" v-if="translateType">
<label class="item-title">本地翻译</label>
@@ -88,14 +87,14 @@ function save() {
<div class="mini-row" v-else>
<label class="item-title">网络翻译</label>
<div class="wrapper">
<el-select v-model="networkTranslateEngine" class="m-2" placeholder="Select" size="small">
<el-option
<ElSelect v-model="networkTranslateEngine" class="m-2" placeholder="Select" size="small">
<ElOption
v-for="item in TranslateEngine"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</ElSelect>
</div>
</div>
<div class="footer">

View File

@@ -1,6 +1,7 @@
<script setup lang="ts">
import {splitEnArticle2} from "@/hooks/article.ts";
import test from '../../test/test.vue'
import BaseButton from "@/components/BaseButton.vue";
function test1() {
splitEnArticle2(
@@ -19,8 +20,8 @@ function test2() {
<template>
<div class="word flex center h-screen ">
<El-Button @click="test1">test1</El-Button>
<El-Button @click="test2">test2</El-Button>
<base-button @click="test1">test1</base-button>
<base-button @click="test2">test2</base-button>
<test/>
</div>
</template>

View File

@@ -99,7 +99,7 @@ const {toggleTheme} = useTheme()
flex-shrink: 0;
&:hover {
background: var(--color-primary);
background: var(--color-select-bg);
color: white;
}

View File

@@ -5,7 +5,7 @@ import {DictId, getDefaultDict} from "@/types";
import BasePage from "@/pages/pc/components/BasePage.vue";
import {computed, onMounted, reactive} from "vue";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {assign, cloneDeep} from "lodash-es";
import {assign, cloneDeep} from "@/utils";
import {nanoid} from "nanoid";
import BaseIcon from "@/components/BaseIcon.vue";
import BaseTable from "@/pages/pc/components/BaseTable.vue";
@@ -18,6 +18,7 @@ import {useRoute, useRouter} from "vue-router";
import {useBaseStore} from "@/stores/base.ts";
import EditBook from "@/pages/pc/article/components/EditBook.vue";
import {_getDictDataByUrl, _nextTick, convertToWord} from "@/utils";
import {ElForm, ElFormItem, ElInput, ElMessage} from "element-plus";
const runtimeStore = useRuntimeStore()
const base = useBaseStore()
@@ -261,85 +262,86 @@ defineRender(() => {
<div class="common-title">
{wordForm.id ? '修改' : '添加'}单词
</div>
<el-form
<ElForm
class="flex-1 overflow-auto pr-2"
ref={e => wordFormRef = e}
rules={wordRules}
model={wordForm}
label-width="7rem">
<el-form-item label="单词" prop="word">
<el-input
<ElFormItem label="单词" prop="word">
<ElInput
modelValue={wordForm.word}
onUpdate:modelValue={e => wordForm.word = e}
/>
</el-form-item>
<el-form-item label="英音音标">
<el-input
</ElFormItem>
<ElFormItem label="英音音标">
<ElInput
modelValue={wordForm.phonetic0}
onUpdate:modelValue={e => wordForm.phonetic0 = e}
/>
</el-form-item>
<el-form-item label="美音音标">
<el-input
</ElFormItem>
<ElFormItem label="美音音标">
<ElInput
modelValue={wordForm.phonetic1}
onUpdate:modelValue={e => wordForm.phonetic1 = e}/>
</el-form-item>
<el-form-item label="翻译">
<el-input
</ElFormItem>
<ElFormItem label="翻译">
<ElInput
modelValue={wordForm.trans}
onUpdate:modelValue={e => wordForm.trans = e}
placeholder="一行一个翻译前面词性后面内容如n.取消);多个翻译请换行"
autosize={{minRows: 6, maxRows: 10}}
type="textarea"/>
</el-form-item>
<el-form-item label="例句">
<el-input
</ElFormItem>
<ElFormItem label="例句">
<ElInput
modelValue={wordForm.sentences}
onUpdate:modelValue={e => wordForm.sentences = e}
placeholder="一行原文,一行译文;多个请换两行"
autosize={{minRows: 6, maxRows: 10}}
type="textarea"/>
</el-form-item>
<el-form-item label="短语">
<el-input
</ElFormItem>
<ElFormItem label="短语">
<ElInput
modelValue={wordForm.phrases}
onUpdate:modelValue={e => wordForm.phrases = e}
placeholder="一行原文,一行译文;多个请换两行"
autosize={{minRows: 6, maxRows: 10}}
type="textarea"/>
</el-form-item>
<el-form-item label="同义词">
<el-input
</ElFormItem>
<ElFormItem label="同义词">
<ElInput
modelValue={wordForm.synos}
onUpdate:modelValue={e => wordForm.synos = e}
placeholder="请参考已有单词格式"
autosize={{minRows: 6, maxRows: 20}}
type="textarea"/>
</el-form-item>
<el-form-item label="同根词">
<el-input
</ElFormItem>
<ElFormItem label="同根词">
<ElInput
modelValue={wordForm.relWords}
onUpdate:modelValue={e => wordForm.relWords = e}
placeholder="请参考已有单词格式"
autosize={{minRows: 6, maxRows: 20}}
type="textarea"/>
</el-form-item>
<el-form-item label="词源">
<el-input
</ElFormItem>
<ElFormItem label="词源">
<ElInput
modelValue={wordForm.etymology}
onUpdate:modelValue={e => wordForm.etymology = e}
placeholder="请参考已有单词格式"
autosize={{minRows: 6, maxRows: 10}}
type="textarea"/>
</el-form-item>
</el-form>
</ElFormItem>
</ElForm>
<div class="center">
<el-button
<base-button
type="info"
onClick={closeWordForm}>关闭
</el-button>
<el-button type="primary"
onClick={onSubmitWord}>保存
</el-button>
</base-button>
<base-button type="primary"
onClick={onSubmitWord}>保存
</base-button>
</div>
</div>
) : null

View File

@@ -13,7 +13,7 @@ import BackIcon from "@/pages/pc/components/BackIcon.vue";
import DictGroup from "@/pages/pc/components/list/DictGroup.vue";
import {useBaseStore} from "@/stores/base.ts";
import {useRouter} from "vue-router";
import {groupBy} from "lodash-es";
import {groupBy} from "@/utils";
import {dictionaryResources} from "@/assets/dictionary.ts";
import {computed} from "vue";

View File

@@ -10,7 +10,7 @@ import {getDefaultWord, ShortcutKey, StudyData, Word} from "@/types.ts";
import {useOnKeyboardEventListener, useStartKeyboardEventListener} from "@/hooks/event.ts";
import useTheme from "@/hooks/theme.ts";
import {getCurrentStudyWord, useWordOptions} from "@/hooks/dict.ts";
import {cloneDeep, shuffle} from "lodash-es";
import {_getDictDataByUrl, cloneDeep, shuffle} from "@/utils";
import {useRoute, useRouter} from "vue-router";
import {Icon} from "@iconify/vue";
import Footer from "@/pages/pc/word/components/Footer.vue";
@@ -23,7 +23,6 @@ import Empty from "@/components/Empty.vue";
import {useBaseStore} from "@/stores/base.ts";
import {usePracticeStore} from "@/stores/practice.ts";
import {dictionaryResources} from "@/assets/dictionary.ts";
import {_getDictDataByUrl} from "@/utils";
interface IProps {
new: Word[],

View File

@@ -14,7 +14,8 @@ import {getCurrentStudyWord} from "@/hooks/dict.ts";
import {useRuntimeStore} from "@/stores/runtime.ts";
import Book from "@/pages/pc/components/Book.vue";
import PopConfirm from "@/pages/pc/components/PopConfirm.vue";
import {ElMessage} from 'element-plus';
import {ElMessage, ElProgress, ElSlider} from 'element-plus';
import BaseButton from "@/components/BaseButton.vue";
const store = useBaseStore()
const router = useRouter()
@@ -184,7 +185,7 @@ const progressTextRight = $computed(() => {
<span>{{ progressTextLeft }}</span>
<span>{{ progressTextRight }} / {{ store.sdict.words.length }}</span>
</div>
<el-progress class="mt-1" :percentage="store.currentStudyProgress" :show-text="false"></el-progress>
<ElProgress class="mt-1" :percentage="store.currentStudyProgress" :show-text="false"></ElProgress>
</div>
<div class="text-sm text-align-end">
预计完成日期{{ _getAccomplishDate(store.sdict.words.length, store.sdict.perDayStudyNumber) }}
@@ -219,12 +220,12 @@ const progressTextRight = $computed(() => {
</div>
个单词 <span class="color-blue cursor-pointer" @click="setPerDayStudyNumber">更改</span>
</div>
<div class="btn">开始学习</div>
<div class="rounded-xl bg-slate-800 flex items-center gap-2 py-3 px-5 text-white cursor-pointer"
:class="store.sdict.name || 'opacity-70 cursor-not-allowed'" @click="startStudy">
<span>开始学习</span>
<Icon icon="icons8:right-round" class="text-2xl"/>
</div>
<BaseButton :disabled="!store.sdict.name" @click="startStudy">
<div class="flex items-center gap-2">
<span>开始学习</span>
<Icon icon="icons8:right-round" class="text-2xl"/>
</div>
</BaseButton>
</div>
</div>
@@ -278,8 +279,8 @@ const progressTextRight = $computed(() => {
<div class="center text-sm" :style="{ opacity: tempPerDayStudyNumber === 20 ? 1 : 0 }">
推荐
</div>
<el-slider :min="10" :step="10" show-stops :marks="{ 10: '10', 200: '200' }" size="small" class="my-6"
:max="200" v-model="tempPerDayStudyNumber"/>
<ElSlider :min="10" :step="10" show-stops :marks="{ 10: '10', 200: '200' }" size="small" class="my-6"
:max="200" v-model="tempPerDayStudyNumber"/>
<div class="flex gap-2 mb-2 mt-10 items-center">
<div>预计</div>
<span class="text-2xl" style="color:rgb(176,116,211)">{{

View File

@@ -9,6 +9,7 @@ import {Icon} from "@iconify/vue";
import IconWrapper from "@/pages/pc/components/IconWrapper.vue";
import Tooltip from "@/pages/pc/components/Tooltip.vue";
import TranslateSetting from "@/pages/pc/components/toolbar/TranslateSetting.vue";
import {ElProgress} from 'element-plus';
const statisticsStore = usePracticeStore()
const settingStore = useSettingStore()
@@ -85,7 +86,7 @@ const progress = $computed(() => {
</Tooltip>
<div class="bottom">
<el-progress
<ElProgress
:percentage="progress"
:stroke-width="8"
:show-text="false"/>
@@ -171,7 +172,7 @@ const progress = $computed(() => {
</div>
</div>
<div class="progress">
<el-progress :percentage="progress"
<ElProgress :percentage="progress"
:stroke-width="8"
:show-text="false"/>
</div>

View File

@@ -42,6 +42,6 @@ function escapeRegExp(string: string): string {
<style scoped lang="scss">
:deep(.highlight-word) {
color: var(--color-primary);
color: var(--color-icon-hightlight);
}
</style>

View File

@@ -323,7 +323,7 @@ function mouseleave() {
cursor: pointer;
&.active {
border-bottom: 2px solid var(--color-font-1);
border-bottom: 2px solid var(--color-font-2);
}
}
}

View File

@@ -1,10 +1,11 @@
<script setup lang="ts">
// import origin from './data.json'
import BaseButton from "@/components/BaseButton.vue";
import {checkAndUpgradeSaveDict, shakeCommonDict} from "@/utils";
import {checkAndUpgradeSaveDict} from "@/utils";
import localforage from "localforage";
import {SAVE_DICT_KEY} from "@/utils/const.ts";
import str from './data.json'
import {ElTableV2} from 'element-plus'
let data = {}
let origin = {}
@@ -73,7 +74,7 @@ const data1 = generateData(columns, 1000)
<BaseButton @click="set">设置data.json的数据到localforage</BaseButton>
<BaseButton @click="check">检测升级逻辑</BaseButton>
</div>
<el-table-v2
<ElTableV2
:columns="columns"
:data="data1"
:width="700"

View File

@@ -52,7 +52,8 @@ export const routes: RouteRecordRaw[] = [
]
const router = VueRouter.createRouter({
history: VueRouter.createWebHistory(),
// history: VueRouter.createWebHistory(),
history: VueRouter.createWebHashHistory(),
routes,
scrollBehavior(to, from, savedPosition) {
// console.log('savedPosition', savedPosition)

View File

@@ -1,6 +1,6 @@
import {defineStore} from 'pinia'
import {Dict, DictId, getDefaultDict, Word} from "../types.ts"
import {cloneDeep, merge} from "lodash-es";
import {cloneDeep} from "@/utils";
import * as localforage from "localforage";
import {nanoid} from "nanoid";
import {SAVE_DICT_KEY} from "@/utils/const.ts";

View File

@@ -1,8 +1,7 @@
import {defineStore} from "pinia"
import {cloneDeep, merge} from "lodash-es";
import {checkAndUpgradeSaveSetting, cloneDeep} from "@/utils";
import {DefaultShortcutKeyMap} from "@/types.ts";
import {SAVE_SETTING_KEY} from "@/utils/const.ts";
import {checkAndUpgradeSaveDict, checkAndUpgradeSaveSetting} from "@/utils";
export interface SettingState {
showToolbar: boolean,

View File

@@ -1,6 +1,6 @@
import {Dict, DictResource, getDefaultDict} from "@/types.ts";
import {getDictFile} from "@/utils/index.ts";
import {cloneDeep} from "lodash-es";
import {cloneDeep} from "@/utils";
import {nanoid} from "nanoid";
export async function getArticleBookDataByUrl(val: DictResource) {

View File

@@ -1,27 +1,27 @@
import axios, {AxiosInstance} from 'axios'
// import globalMethods from './global-methods'
// import Config from '../config/index'
// import CONSTANT from './const_var'
// import store from '../store'
// import Storage from './storage'
export const axiosInstance: AxiosInstance = axios.create({
// baseURL: process.env.NODE_ENV === 'production' ? Config.PRODUCT_API_URL : Config.API_URL,
// baseURL: 'http://testtestgp.com',
timeout: 15000,
})
// request 拦截器
axiosInstance.interceptors.request.use(
(config) => {
// console.log('config', config)
if (config.url === 'https://api.fanyi.baidu.com/api/trans/vip/translate') {
config.url = '/baidu'
}
return config
},
error => Promise.reject(error),
)
// import axios, {AxiosInstance} from 'axios'
// // import globalMethods from './global-methods'
// // import Config from '../config/index'
// // import CONSTANT from './const_var'
// // import store from '../store'
// // import Storage from './storage'
//
// export const axiosInstance: AxiosInstance = axios.create({
// // baseURL: process.env.NODE_ENV === 'production' ? Config.PRODUCT_API_URL : Config.API_URL,
// // baseURL: 'http://testtestgp.com',
// timeout: 15000,
// })
//
// // request 拦截器
// axiosInstance.interceptors.request.use(
// (config) => {
// // console.log('config', config)
// if (config.url === 'https://api.fanyi.baidu.com/api/trans/vip/translate') {
// config.url = '/baidu'
// }
// return config
// },
// error => Promise.reject(error),
// )
// respone 拦截器
// instance.interceptors.response.use(
@@ -111,4 +111,4 @@ axiosInstance.interceptors.request.use(
// })
// }
// export default request
// export default request

View File

@@ -1,9 +1,7 @@
import {SAVE_DICT_KEY, SAVE_SETTING_KEY} from "@/utils/const.ts";
import {BaseState, DefaultBaseState} from "@/stores/base.ts";
import {getDefaultSettingState, SettingState} from "@/stores/setting.ts";
import {cloneDeep} from "lodash-es";
import {getDefaultSettingState} from "@/stores/setting.ts";
import {Dict, DictResource, DictType, getDefaultArticle, getDefaultDict, getDefaultWord} from "@/types.ts";
import {ArchiveReader, libarchiveWasm} from "libarchive-wasm";
import {useRouter} from "vue-router";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {nanoid} from "nanoid";
@@ -514,3 +512,57 @@ export function convertToWord(raw: any) {
custom: true
});
}
export function cloneDeep<T>(val: T) {
return JSON.parse(JSON.stringify(val))
}
export function shuffle<T>(array: T[]): T[] {
const result = array.slice(); // 复制数组,避免修改原数组
for (let i = result.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1)); // 生成 0 ~ i 的随机索引
[result[i], result[j]] = [result[j], result[i]]; // 交换元素
}
return result;
}
export function last<T>(array: T[]): T | undefined {
return array.length > 0 ? array[array.length - 1] : undefined;
}
export function debounce<T extends (...args: any[]) => void>(func: T, wait: number) {
let timer: ReturnType<typeof setTimeout> | null = null;
return function (this: ThisParameterType<T>, ...args: Parameters<T>) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, args);
}, wait);
};
}
export function throttle<T extends (...args: any[]) => void>(func: T, wait: number) {
let lastTime = 0;
return function (this: ThisParameterType<T>, ...args: Parameters<T>) {
const now = Date.now();
if (now - lastTime >= wait) {
func.apply(this, args);
lastTime = now;
}
};
}
export function reverse<T>(array: T[]): T[] {
return array.slice().reverse();
}
export function assign<T extends object, U extends object>(target: T, ...sources: U[]): T & U {
return Object.assign(target, ...sources);
}
export function groupBy<T extends Record<string, any>>(array: T[], key: string) {
return array.reduce<Record<string, T[]>>((result, item) => {
const groupKey = String(item[key]);
(result[groupKey] ||= []).push(item);
return result;
}, {});
}