28 Commits

Author SHA1 Message Date
zyronon
3ba825a6e4 feat:删除git issue模板 2025-07-19 23:44:41 +08:00
zyronon
0c0b4eec6f Merge remote-tracking branch 'origin/master' 2025-07-18 02:27:24 +08:00
zyronon
54e411310d feat:删除git issue模板 2025-07-18 02:26:58 +08:00
Zyronon
2827bcaa17 Update README.md 2025-07-12 18:34:16 +08:00
Zyronon
c35e8d3f45 Update README.md 2025-07-12 18:33:46 +08:00
Zyronon
6c32ecdaff Update README.md 2025-07-12 18:32:42 +08:00
zyronon
e8a89b8fb3 feat:添加域名检查提示 2025-07-11 20:28:45 +08:00
zyronon
627107d930 feat:添加域名检查提示 2025-07-11 20:26:33 +08:00
Zyronon
a179710ba2 Update README.md 2025-07-04 11:48:22 +08:00
Zyronon
84bf0a05a9 Update README.md 2025-07-04 11:47:47 +08:00
Zyronon
a0ef147b10 Update index.html 2025-07-03 20:17:17 +08:00
Zyronon
6e8b5bb807 Update README.md 2025-07-03 20:13:12 +08:00
Zyronon
aee56efdf5 Update README.md 2025-07-02 21:23:27 +08:00
Zyronon
b4a26b2c97 Update deploy-pages.yml 2025-06-30 23:19:34 +08:00
Zyronon
75cd9d6aa3 Update deploy-pages.yml 2025-06-30 23:17:58 +08:00
Zyronon
d5b88ea982 Update deploy-pages.yml 2025-06-30 23:15:40 +08:00
Zyronon
f54c8d168b Update index.html 2025-06-30 23:12:56 +08:00
Zyronon
2904bda8ef Update README.md 2025-06-30 23:10:33 +08:00
Zyronon
64f1bd6161 Merge pull request #25 from Cassianvale/master
fix: 修复导入xlsx的无法智能分配章节
2024-07-18 13:03:45 +08:00
Cassianvale
10f9e21da1 fix: 修复导入xlsx的无法智能分配章节 2024-07-17 15:12:17 +08:00
zyronon
0237db6273 save 2024-05-07 14:04:28 +08:00
zyronon
d2753d04b1 add docker support 2024-01-10 16:07:45 +08:00
zyronon
6b06a9174f fix bug 2024-01-03 10:35:19 +08:00
zyronon
95a63465c5 Small optimization 2024-01-03 10:28:46 +08:00
zyronon
4464dde128 fix bug 2024-01-02 02:06:43 +08:00
zyronon
5cad026983 No statistics at the time of development 2023-12-13 11:11:26 +08:00
Zyronon
9edb27d119 Merge pull request #12 from zyronon/dev
merge dev
2023-12-10 16:45:26 +08:00
Zyronon
8739176f0f Merge pull request #11 from zyronon/dev
Dev
2023-12-08 01:29:28 +08:00
22 changed files with 5042 additions and 4106 deletions

View File

@@ -1,20 +0,0 @@
---
name: 功能请求 | Feature request
about: 创建一个功能请求 | Create a feature request
title: 功能请求 | Feature request
labels: ''
assignees: ''
---
1、描述
请尽可能详细描述您需要的特性。
2、这个功能解决了什么问题
请尽可能详尽地说明这个需求的用例和场景
1. Description
Please provide as detailed a description as possible of the features you need.
2. What problem does this feature solve?
Please provide as detailed a description of the use cases and scenarios for this requirement as possible

View File

@@ -1,33 +0,0 @@
---
name: 单词错误 | Word error
about: 创建一个单词释义错误、音标错误的报告 | Create a report of incorrect word definitions and phonetic
errors
title: 单词错误 | Word error
labels: ''
assignees: ''
---
1、词典名字
在这里填写词典名字
2、单词
在这里填写单词
3、错误内容
在这里填写错误内容
4、截图
可以直接按Ctrl + V 复制在这里(复制成功后会显示一串地址)
1. Dictionary name
Fill in the dictionary name here
2. Words
Fill in the words here
3. Error content
Fill in the incorrect content here
4. Screenshot
You can directly press Ctrl+V to copy here (a string of addresses will be displayed after successful copying)

View File

@@ -1,24 +0,0 @@
---
name: '问题报告 | Bug report '
about: '创建一个问题报告 | Create a bug report '
title: '问题报告 | Bug report '
labels: ''
assignees: ''
---
### 注意:请确认问题可以在浏览器的 无痕模式 下复现,而不是自己安装的其他插件或脚本导致的
1、描述
在这里填写问题描述
2、截图
可以直接按Ctrl + V 复制在这里(复制成功后会显示一串地址)
### Note: Please confirm that the problem can be reproduced in the browser's traceless mode, rather than being caused by other plugins or scripts installed by yourself
1. Description
Fill in the problem description here
2. Screenshot
You can directly press Ctrl+V to copy here (a string of addresses will be displayed after successful copying)

View File

@@ -29,15 +29,15 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v2
uses: pnpm/action-setup@v4
with:
version: 8
- name: Set up Node
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 18
cache: 'pnpm'
@@ -47,13 +47,17 @@ jobs:
- name: Build
run: pnpm run build
- name: Setup Pages
uses: actions/configure-pages@v3
uses: actions/configure-pages@v4
- name: Upload artifact
uses: actions/upload-pages-artifact@v1
uses: actions/upload-pages-artifact@v3
with:
# Upload dist repository
path: './dist'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v1
uses: actions/deploy-pages@v4

6
Dockerfile Normal file
View File

@@ -0,0 +1,6 @@
FROM node
COPY . /root/typing-word
WORKDIR /root/typing-word
EXPOSE 3000
RUN npm install
CMD ["npm", "start"]

View File

@@ -10,13 +10,16 @@
<a href="https://github.com/zyronon/type-word/blob/master/LICENSE"><img src="https://img.shields.io/github/license/zyronon/type-word" alt="License"></a>
<a><img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg"/></a>
<a><img src="https://img.shields.io/badge/Powered%20by-Vue-blue"/></a>
<a href="https://hellogithub.com/repository/eb70616d65604458908fc1736e7d41fc" target="_blank"><img src="https://abroad.hellogithub.com/v1/widgets/recommend.svg?rid=eb70616d65604458908fc1736e7d41fc&claim_uid=k5e4ZAqRjJEGzCW&theme=small" alt="FeaturedHelloGitHub" /></a>
</p>
<div align=center>
<a href="https://trendshift.io/repositories/14139" target="_blank" class="trendshift-badge"><img src="https://trendshift.io/api/badge/repositories/14139" alt="TypeWords | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
</div>
## 📸 在线访问
Github Pages: <https://typing-word.ttentau.top>(国内推荐访问这个)
Netlify: <https://typing-words.netlify.app/>(需要翻墙)
Github Pages: <https://2study.top>
## 🛠 功能列表
@@ -57,22 +60,9 @@ API 等词库。 尽可能满足大部分用户对背单词的需求,也非常
- 王陆雅思王听力语料库
- 日语常见词、N1 N5
## 📗 API 词库
- JavaScript API、Node.js API、Java API、Linux Command、C#: List API
词库均来源于(除文章以外)[qwerty-learner](https://github.com/RealKai42/qwerty-learner/)
如果您需要背诵其他词库,欢迎在 Issue 中提出
## 🎙 功能与建议
目前项目处于开发初期,新功能正在持续添加中,如果你对软件有任何功能与建议,欢迎在 Issues 中提出
如果你也喜欢本软件的设计思想,欢迎提交 pr非常感谢你对我们的支持
### 灵感来源
[qwerty-learner](https://github.com/RealKai42/qwerty-learner/) 很喜欢作者的这个项目,但是它没有背单词所必备的 **生词本、错词本、简单词** 的功能,可能是作者反复强调和提醒这个项目是“**为键盘工作者设计的单词记忆与英语肌肉记忆锻炼软件**”而不是一个“**背单词**”的软件吧,尽管绝大多数用户都是用它来背单词😂😂😂。
本项目参考其思路使用 Vue 重写了,并添加了 **生词本、错词本、简单词****文章练习** 等功能

1
components.d.ts vendored
View File

@@ -44,6 +44,7 @@ declare module 'vue' {
Empty: typeof import('./src/components/Empty.vue')['default']
FeedbackModal: typeof import('./src/components/toolbar/FeedbackModal.vue')['default']
Fireworks: typeof import('./src/components/Fireworks.vue')['default']
HostNotice: typeof import('./src/components/HostNotice.vue')['default']
IconWrapper: typeof import('./src/components/IconWrapper.vue')['default']
Input: typeof import('./src/components/Input.vue')['default']
List: typeof import('./src/components/list/List.vue')['default']

View File

@@ -29,19 +29,19 @@
</script>
<script>
var _hmt = _hmt || [];
(function () {
var hm = document.createElement("script");
if (location.href.includes('netlify.app')) {
hm.src = "https://hm.baidu.com/hm.js?d77525f1ad23698a2f34f54ff69c7750";
} else if (location.href.includes('github.io')) {
hm.src = "https://hm.baidu.com/hm.js?d77525f1ad23698a2f34f54ff69c7750";
} else {
hm.src = "https://hm.baidu.com/hm.js?c558fd8cc39d4cc233a9db44ff5b6e3f";
}
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
if (!location.href.includes('localhost')
&& !location.href.includes('192.168')
&& !location.href.includes('172.16')
&& !location.href.includes('10.0')
) {
var _hmt = _hmt || [];
(function () {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?3dae52fcd5375a19905462e4ad3eb54e";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
}
</script>
</head>
<body>

View File

@@ -17,51 +17,51 @@
"i18n:write": "gulp i18nwrite"
},
"dependencies": {
"@opentranslate/baidu": "^1.4.2",
"@opentranslate/translator": "^1.4.2",
"axios": "^1.5.0",
"compromise": "^14.10.0",
"copy-to-clipboard": "^3.3.3",
"element-plus": "^2.3.9",
"file-saver": "^2.0.5",
"git-last-commit": "^1.0.1",
"hover.css": "^2.3.2",
"localforage": "^1.10.0",
"lodash-es": "^4.17.21",
"mitt": "^3.0.1",
"nanoid": "^5.0.3",
"pinia": "^2.1.6",
"sentence-splitter": "^4.2.1",
"tesseract.js": "^4.1.1",
"vue": "^3.3.4",
"vue-activity-calendar": "^1.2.2",
"@opentranslate/baidu": "1.4.2",
"@opentranslate/translator": "1.4.2",
"axios": "1.5.0",
"compromise": "14.10.0",
"copy-to-clipboard": "3.3.3",
"element-plus": "2.3.9",
"file-saver": "2.0.5",
"git-last-commit": "1.0.1",
"hover.css": "2.3.2",
"localforage": "1.10.0",
"lodash-es": "4.17.21",
"mitt": "3.0.1",
"nanoid": "5.0.3",
"pinia": "2.1.6",
"sentence-splitter": "4.2.1",
"tesseract.js": "4.1.1",
"vue": "3.3.13",
"vue-activity-calendar": "1.2.2",
"vue-i18n": "9",
"vue-router": "4",
"vue-virtual-scroller": "2.0.0-beta.8"
},
"devDependencies": {
"@iconify/vue": "^4.1.1",
"@types/file-saver": "^2.0.5",
"@types/lodash-es": "^4.17.9",
"@types/uuid": "^9.0.4",
"@vitejs/plugin-vue": "^4.2.3",
"@vitejs/plugin-vue-jsx": "^3.0.1",
"@vue/compiler-sfc": "^3.3.4",
"commitizen": "^4.3.0",
"cz-conventional-changelog": "^3.3.0",
"esm": "^3.2.25",
"gulp": "^4.0.2",
"husky": "^8.0.3",
"push-dir": "^0.4.1",
"rollup-plugin-visualizer": "^5.9.2",
"sass": "^1.64.2",
"tslib": "^2.6.2",
"typescript": "^5.2.0",
"unplugin-auto-import": "^0.16.6",
"unplugin-vue-components": "^0.25.2",
"vite": "^4.4.5",
"vue-tsc": "^1.8.5",
"xlsx": "^0.18.5"
"@iconify/vue": "4.1.1",
"@types/file-saver": "2.0.5",
"@types/lodash-es": "4.17.9",
"@types/uuid": "9.0.4",
"@vitejs/plugin-vue": "4.2.3",
"@vitejs/plugin-vue-jsx": "3.0.1",
"@vue/compiler-sfc": "3.3.4",
"commitizen": "4.3.0",
"cz-conventional-changelog": "3.3.0",
"esm": "3.2.25",
"gulp": "4.0.2",
"husky": "8.0.3",
"push-dir": "0.4.1",
"rollup-plugin-visualizer": "5.9.2",
"sass": "1.64.2",
"tslib": "2.6.2",
"typescript": "5.4.5",
"unplugin-auto-import": "0.16.6",
"unplugin-vue-components": "0.25.2",
"vite": "4.4.5",
"vue-tsc": "1.8.5",
"xlsx": "0.18.5"
},
"config": {
"commitizen": {

7784
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -15,6 +15,7 @@ import CollectNotice from "@/components/CollectNotice.vue";
import {SAVE_SETTING_KEY, SAVE_DICT_KEY} from "@/utils/const.ts";
import {shakeCommonDict} from "@/utils";
import router from "@/router.ts";
import HostNotice from "@/components/HostNotice.vue";
const store = useBaseStore()
const runtimeStore = useRuntimeStore()
@@ -83,6 +84,7 @@ onMounted(() => {
<Backgorund/>
<router-view/>
<CollectNotice/>
<HostNotice/>
<ArticleContentDialog/>
<SettingDialog/>
</template>

View File

@@ -84,7 +84,7 @@ onMounted(() => {
}
}
drawMoon()
// drawMoon()
})
</script>

View File

@@ -49,7 +49,7 @@ const isMac = /macintosh|mac os x/i.test(navigator.userAgent);
<div class="collect" v-if="showNotice">
<div class="href-wrapper">
<div class="round">
<div class="href">typing-word.ttentau.top</div>
<div class="href">2study.top</div>
<Icon
width="22"
icon="mdi:star-outline"/>
@@ -179,4 +179,4 @@ const isMac = /macintosh|mac os x/i.test(navigator.userAgent);
}
}
</style>
</style>

View File

@@ -0,0 +1,140 @@
<script setup lang="ts">
import BaseButton from "@/components/BaseButton.vue";
import {watch} from "vue";
import {useSettingStore} from "@/stores/setting.ts";
let settingStore = useSettingStore()
let show = $ref(false)
function toggleNotice() {
show = false
}
watch(() => settingStore.load, (n) => {
const params = new URLSearchParams(window.location.search);
if (params.get('from') === 'redirect') {
show = true
}
})
</script>
<template>
<transition name="right">
<div class="HostNotice" v-if="show">
<div class="notice">
<div>检查到您是通过老域名 typing-word.ttentau.top 访问的本网站特此弹窗提示</div>
<p>老域名已不再续费7天后过期将无法访问请更换为新域名 <span class="active"><a href="https://2study.top">2study.top</a></span>
访问</p>
</div>
<div class="wrapper">
<BaseButton size="large" @click="toggleNotice">关闭</BaseButton>
</div>
</div>
</transition>
</template>
<style scoped lang="scss">
.right-enter-active,
.right-leave-active {
transition: all .5s ease;
}
.right-enter-from,
.right-leave-to {
transform: translateX(110%);
}
.HostNotice {
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
z-index: 2;
font-size: 20rem;
display: flex;
flex-direction: column;
align-items: center;
background: var(--color-second-bg);
padding: 30rem;
border-radius: 12rem;
width: 500rem;
color: var(--color-font-1);
line-height: 1.5;
border: 1px solid var(--color-item-border);
box-shadow: var(--shadow);
.notice {
margin-top: 30rem;
}
.active {
color: var(--color-main-active);
}
.wrapper {
.collect {
display: flex;
flex-direction: column;
align-items: center;
.href-wrapper {
display: flex;
font-size: 16rem;
align-items: center;
gap: 10rem;
.round {
color: var(--color-font-1);
border-radius: 50rem;
padding: 10rem 10rem;
padding-left: 20rem;
gap: 30rem;
display: flex;
align-items: center;
justify-content: space-between;
background: var(--color-main-bg);
.href {
font-size: 14rem;
}
}
.star {
color: var(--color-main-active);
}
.right {
display: flex;
align-items: center;
}
}
.collect-keyboard {
margin-top: 20rem;
font-size: 16rem;
span {
margin-left: 10rem;
}
}
}
}
.close-wrapper {
right: var(--space);
top: var(--space);
position: absolute;
font-size: 14rem;
display: flex;
justify-content: flex-end;
align-items: center;
color: var(--color-font-1);
gap: 10rem;
}
}
</style>

View File

@@ -1,20 +1,20 @@
<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 {$computed, $ref} from "vue/macros";
import {cloneDeep} from "lodash-es";
import {DefaultShortcutKeyMap, Dict, DictType, ShortcutKey} from "@/types.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 { $computed, $ref } from "vue/macros";
import { cloneDeep } from "lodash-es";
import { DefaultShortcutKeyMap, Dict, DictType, 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 { APP_NAME, EXPORT_DATA_KEY, SAVE_DICT_KEY, SAVE_SETTING_KEY, SoundFileOptions } from "@/utils/const.ts";
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
import {BaseState, useBaseStore} from "@/stores/base.ts";
import { BaseState, useBaseStore } from "@/stores/base.ts";
import * as copy from "copy-to-clipboard";
import {saveAs} from "file-saver";
import {checkAndUpgradeSaveDict, checkAndUpgradeSaveSetting, shakeCommonDict} from "@/utils";
import {dayjs} from "element-plus";
import { saveAs } from "file-saver";
import { checkAndUpgradeSaveDict, checkAndUpgradeSaveSetting, shakeCommonDict } from "@/utils";
import { dayjs } from "element-plus";
const emit = defineEmits<{
@@ -344,16 +344,42 @@ function importData(e) {
<label class="item-title">其他设置</label>
</div>
<div class="row">
<label class="sub-title">切换下一个单词时间</label>
<label class="sub-title">是否自动切换下一个单词</label>
<div class="wrapper">
<el-switch v-model="settingStore.autoNext"
inline-prompt
active-text="开"
inactive-text="关"
/>
</div>
</div>
<div class="desc">
关闭后,当完成单词输入时,需要再次按下空格键切换下一个
</div>
<div class="row">
<label class="sub-title">自动切换下一个单词延迟</label>
<div class="wrapper">
<el-input-number v-model="settingStore.waitTimeForChangeWord"
:min="6"
:max="100"
:min="0"
type="number"
/>
<span>毫秒</span>
</div>
</div>
<div class="row">
<label class="sub-title">默写时是否显示单词长度</label>
<div class="wrapper">
<el-switch v-model="settingStore.dictationShowWordLength"
inline-prompt
active-text="开"
inactive-text="关"
/>
</div>
</div>
<div class="desc">
默写时用下划线 _ 来显示每个字符。关闭后,用空格代替,用户将无法判断单词长度
</div>
</div>
<div class="body" v-if="tabIndex === 2">
<div class="row">

View File

@@ -20,20 +20,11 @@ const emit = defineEmits([
<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>
<a :href="`${GITHUB}/issues/new`"
target="_blank">Issue</a>
</BaseButton>
</div>
</div>
@@ -72,4 +63,4 @@ const emit = defineEmits([
}
}
</style>
</style>

View File

@@ -67,7 +67,7 @@ watch(() => store.load, n => {
<Tooltip
:title="`词典详情(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.OpenDictDetail]})`">
<div class="info hvr-grow" @click="emitter.emit(EventKey.openDictModal,'detail')">
{{ store.currentDict.name }} {{ practiceStore.repeatNumber ? ' 复习错词' : '' }}
{{ store.currentDict.name }}
</div>
</Tooltip>
<ChapterName v-if="store.currentDict.type === DictType.word"/>

View File

@@ -275,9 +275,10 @@ function toChapterWordList() {
)
}
//同步到我的词典列表
//同步到我的词典列表,更新词典单词总数
function syncEditDict2MyDictList() {
syncMyDictList(runtimeStore.editDict)
runtimeStore.editDict.length = runtimeStore.editDict.words.length;
syncMyDictList(runtimeStore.editDict);
}
@@ -486,9 +487,9 @@ function exportData(type: string) {
}
function importData(e: any) {
let file = e.target.files[0]
if (!file) return
// no()
let file = e.target.files[0];
if (!file) return;
let reader = new FileReader();
reader.onload = function (e) {
let data = e.target.result;
@@ -505,32 +506,36 @@ function importData(e: any) {
usphone: String(v['音标①'] ?? ''),
ukphone: String(v['音标②'] ?? ''),
trans: String(v['释义(一行一个释义)'] ?? '').split('\n')
}
return word
};
return word;
}
}).filter(v => v)
}).filter(v => v);
checkRepeatWord(words, residueWordList, noRepeatWords => {
residueWordList = residueWordList.concat(noRepeatWords)
syncEditDict2MyDictList()
setTimeout(residueWordListRef?.scrollToBottom, 100)
residueWordList = residueWordList.concat(noRepeatWords);
// 同步到editDict中的words和originWords
runtimeStore.editDict.words = runtimeStore.editDict.words.concat(noRepeatWords);
runtimeStore.editDict.originWords = runtimeStore.editDict.originWords.concat(noRepeatWords);
syncEditDict2MyDictList();
setTimeout(residueWordListRef?.scrollToBottom, 100);
},
repeatWords => {
repeatWords.map(v => {
residueWordList[v.index] = v
delete residueWordList[v.index].index
})
syncEditDict2MyDictList()
setTimeout(residueWordListRef?.scrollToBottom, 100)
residueWordList[v.index] = v;
delete residueWordList[v.index].index;
});
syncEditDict2MyDictList();
setTimeout(residueWordListRef?.scrollToBottom, 100);
},
() => ElMessage.success('导入成功!'))
() => ElMessage.success('导入成功!'));
} else {
ElMessage.warning('导入失败!原因:没有数据')
ElMessage.warning('导入失败!原因:没有数据');
}
};
reader.readAsBinaryString(file);
}
function editDict() {
isEditDict = true
}

View File

@@ -1,14 +1,14 @@
<script setup lang="ts">
import {DefaultWord, ShortcutKey, Word} from "@/types.ts";
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, useTTsPlayAudio} from "@/hooks/sound.ts";
import {emitter, EventKey} from "@/utils/eventBus.ts";
import {cloneDeep} from "lodash-es";
import {onUnmounted, watch, onMounted} from "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, 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 {
@@ -27,6 +27,7 @@ const emit = defineEmits<{
let input = $ref('')
let wrong = $ref('')
let showFullWord = $ref(false)
let waitNext = $ref(false)
//输入锁定因为跳转到下一个单词有延时如果重复在延时期间内重复输入导致会跳转N次
let inputLock = false
let wordRepeatCount = 0
@@ -47,7 +48,7 @@ let displayWord = $computed(() => {
watch(() => props.word, () => {
wrong = input = ''
wordRepeatCount = 0
inputLock = false
waitNext = inputLock = false
if (settingStore.wordSound) {
volumeIconRef?.play(400, true)
}
@@ -79,6 +80,13 @@ function repeat() {
}
async function onTyping(e: KeyboardEvent) {
if (waitNext) {
if (e.code === 'Space') {
emit('next')
waitNext = false
}
return
}
if (inputLock) return
inputLock = true
let letter = e.key
@@ -110,13 +118,23 @@ async function onTyping(e: KeyboardEvent) {
playCorrect()
if (settingStore.repeatCount == 100) {
if (settingStore.repeatCustomCount <= wordRepeatCount + 1) {
setTimeout(() => emit('next'), settingStore.waitTimeForChangeWord)
if (settingStore.autoNext) {
setTimeout(() => emit('next'), settingStore.waitTimeForChangeWord)
} else {
waitNext = true
inputLock = false
}
} else {
repeat()
}
} else {
if (settingStore.repeatCount <= wordRepeatCount + 1) {
setTimeout(() => emit('next'), settingStore.waitTimeForChangeWord)
if (settingStore.autoNext) {
setTimeout(() => emit('next'), settingStore.waitTimeForChangeWord)
} else {
waitNext = true
inputLock = false
}
} else {
repeat()
}
@@ -139,6 +157,8 @@ function del() {
function showWord() {
if (settingStore.allowWordTip) {
showFullWord = true
//如果默写情况下,看了提示也视为输入错误
emit('wrong')
}
}
@@ -163,17 +183,17 @@ defineExpose({del, showWord, hideWord, play})
>
<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 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">
@@ -185,10 +205,10 @@ defineExpose({del, showWord, hideWord, play})
<span class="wrong" v-if="wrong">{{ wrong }}</span>
<template v-if="settingStore.dictation">
<span class="letter" v-if="!showFullWord"
@mouseenter="settingStore.allowWordTip && (showFullWord = true)">{{
displayWord.split('').map(() => '_').join('')
@mouseenter="showWord">{{
displayWord.split('').map(() => settingStore.dictationShowWordLength ? '_' : '&nbsp;').join('')
}}</span>
<span class="letter" v-else @mouseleave="showFullWord = false">{{ displayWord }}</span>
<span class="letter" v-else @mouseleave="hideWord">{{ displayWord }}</span>
</template>
<span class="letter" v-else>{{ displayWord }}</span>
</div>
@@ -198,7 +218,7 @@ defineExpose({del, showWord, hideWord, play})
<VolumeIcon ref="volumeIconRef" :simple="true" :cb="() => playWordAudio(word.name)"/>
</Tooltip>
</div>
<div class="phonetic" v-if="settingStore.wordSoundType === 'us' && word.usphone">[{{ word.usphone}}]</div>
<div class="phonetic" v-if="settingStore.wordSoundType === 'us' && word.usphone">[{{ word.usphone }}]</div>
<div class="phonetic" v-if="settingStore.wordSoundType === 'uk' && word.ukphone">[{{ word.ukphone }}]</div>
</div>
</template>

View File

@@ -1,21 +1,21 @@
<script setup lang="ts">
import {onMounted, onUnmounted, watch} from "vue"
import {$computed, $ref} from "vue/macros"
import {useBaseStore} from "@/stores/base.ts"
import {DefaultDisplayStatistics, DictType, ShortcutKey, Sort, Word} from "../../../types.ts";
import {emitter, EventKey} from "@/utils/eventBus.ts"
import {cloneDeep, reverse, shuffle} from "lodash-es"
import {usePracticeStore} from "@/stores/practice.ts"
import {useSettingStore} from "@/stores/setting.ts";
import {useOnKeyboardEventListener, useWindowClick} from "@/hooks/event.ts";
import {Icon} from "@iconify/vue";
import { onMounted, onUnmounted, watch } from "vue"
import { $computed, $ref } from "vue/macros"
import { useBaseStore } from "@/stores/base.ts"
import { DefaultDisplayStatistics, DictType, ShortcutKey, Sort, Word } from "../../../types.ts";
import { emitter, EventKey } from "@/utils/eventBus.ts"
import { cloneDeep, reverse, shuffle } from "lodash-es"
import { usePracticeStore } from "@/stores/practice.ts"
import { useSettingStore } from "@/stores/setting.ts";
import { useOnKeyboardEventListener, useWindowClick } from "@/hooks/event.ts";
import { Icon } from "@iconify/vue";
import Tooltip from "@/components/Tooltip.vue";
import Options from "@/pages/practice/Options.vue";
import Typing from "@/pages/practice/practice-word/Typing.vue";
import Panel from "@/pages/practice/Panel.vue";
import IconWrapper from "@/components/IconWrapper.vue";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {syncMyDictList, useWordOptions} from "@/hooks/dict.ts";
import { useRuntimeStore } from "@/stores/runtime.ts";
import { syncMyDictList, useWordOptions } from "@/hooks/dict.ts";
import BaseIcon from "@/components/BaseIcon.vue";
import WordList from "@/components/list/WordList.vue";
import Empty from "@/components/Empty.vue";
@@ -99,7 +99,6 @@ const nextWord: Word = $computed(() => {
function next(isTyping: boolean = true) {
if (data.index === data.words.length - 1) {
//复制当前错词,因为第一遍错词是最多的,后续的练习都是从错词中练习
if (stat.total === -1) {
let now = Date.now()
@@ -140,8 +139,7 @@ function next(isTyping: boolean = true) {
data.index++
isTyping && practiceStore.inputWordNumber++
console.log('这个词完了')
if ([DictType.word].includes(store.currentDict.type)
&& store.skipWordNames.includes(word.name.toLowerCase())) {
if (store.skipWordNames.includes(word.name.toLowerCase())) {
next()
}
}

View File

@@ -9,100 +9,100 @@ import {SAVE_DICT_KEY, SAVE_SETTING_KEY} from "@/utils/const.ts";
import {checkAndUpgradeSaveDict} from "@/utils";
export interface BaseState {
myDictList: Dict[],
collectDictIds: string[],
current: {
index: number,
practiceType: DictType,//练习类型目前仅词典为collect时判断是练单词还是文章使用
},
simpleWords: string[],
load: boolean
myDictList: Dict[],
collectDictIds: string[],
current: {
index: number,
practiceType: DictType,//练习类型目前仅词典为collect时判断是练单词还是文章使用
},
simpleWords: string[],
load: boolean
}
export const DefaultBaseState = (): BaseState => ({
myDictList: [
{
...cloneDeep(DefaultDict),
id: 'collect',
name: '收藏',
type: DictType.collect,
category: '自带字典',
tags: ['自带'],
isCustom: true,
},
{
...cloneDeep(DefaultDict),
id: 'skip',
name: '简单词',
type: DictType.simple,
category: '自带字典',
isCustom: true,
},
{
...cloneDeep(DefaultDict),
id: 'wrong',
name: '错词本',
type: DictType.wrong,
category: '自带字典',
isCustom: true,
},
{
...cloneDeep(DefaultDict),
id: 'cet4',
name: 'CET-4',
description: '大学英语四级词库',
category: '中国考试',
tags: ['大学英语'],
url: 'CET4_T.json',
length: 2607,
translateLanguage: 'common',
language: 'en',
type: DictType.word
},
// {
// ...cloneDeep(DefaultDict),
// id: 'article_nce2',
// name: "新概念英语2-课文",
// description: '新概念英语2-课文',
// category: '英语学习',
// tags: ['新概念英语'],
// url: 'NCE_2.json',
// translateLanguage: 'common',
// language: 'en',
// type: DictType.article,
// resourceId: 'article_nce2',
// length: 96
// },
// {
// ...cloneDeep(DefaultDict),
// id: 'nce-new-2',
// name: '新概念英语(新版)-2',
// description: '新概念英语新版第二册',
// category: '青少年英语',
// tags: ['新概念英语'],
// url: 'nce-new-2.json',
// translateLanguage: 'common',
// language: 'en',
// type: DictType.word,
// resourceId: 'nce-new-2',
// length: 862
// },
],
collectDictIds: [],
current: {
index: 3,
// dictType: DictType.article,
// index: 0,
practiceType: DictType.word,
myDictList: [
{
...cloneDeep(DefaultDict),
id: 'collect',
name: '收藏',
type: DictType.collect,
category: '自带字典',
tags: ['自带'],
isCustom: true,
},
simpleWords: [
'a', 'an',
'i', 'my', 'you', 'your', 'me', 'it',
'what', 'who', 'where', 'how', 'when', 'which',
'be', 'am', 'is', 'do', 'are', 'did', 'were', 'was', 'can', 'could', 'will', 'would',
'the', 'that', 'this', 'to', 'of', 'for', 'and', 'at', 'not', 'no', 'yes',
],
load: false
{
...cloneDeep(DefaultDict),
id: 'skip',
name: '简单词',
type: DictType.simple,
category: '自带字典',
isCustom: true,
},
{
...cloneDeep(DefaultDict),
id: 'wrong',
name: '错词本',
type: DictType.wrong,
category: '自带字典',
isCustom: true,
},
{
...cloneDeep(DefaultDict),
id: 'cet4',
name: 'CET-4',
description: '大学英语四级词库',
category: '中国考试',
tags: ['大学英语'],
url: 'CET4_T.json',
length: 2607,
translateLanguage: 'common',
language: 'en',
type: DictType.word
},
// {
// ...cloneDeep(DefaultDict),
// id: 'article_nce2',
// name: "新概念英语2-课文",
// description: '新概念英语2-课文',
// category: '英语学习',
// tags: ['新概念英语'],
// url: 'NCE_2.json',
// translateLanguage: 'common',
// language: 'en',
// type: DictType.article,
// resourceId: 'article_nce2',
// length: 96
// },
// {
// ...cloneDeep(DefaultDict),
// id: 'nce-new-2',
// name: '新概念英语(新版)-2',
// description: '新概念英语新版第二册',
// category: '青少年英语',
// tags: ['新概念英语'],
// url: 'nce-new-2.json',
// translateLanguage: 'common',
// language: 'en',
// type: DictType.word,
// resourceId: 'nce-new-2',
// length: 862
// },
],
collectDictIds: [],
current: {
index: 3,
// dictType: DictType.article,
// index: 0,
practiceType: DictType.word,
},
simpleWords: [
'a', 'an',
'i', 'my', 'you', 'your', 'me', 'it',
'what', 'who', 'where', 'how', 'when', 'which',
'be', 'am', 'is', 'do', 'are', 'did', 'were', 'was', 'can', 'could', 'will', 'would',
'the', 'that', 'this', 'to', 'of', 'for', 'and', 'at', 'not', 'no', 'yes',
],
load: false
})
// words: [
@@ -151,206 +151,206 @@ export const DefaultBaseState = (): BaseState => ({
// ],
export const useBaseStore = defineStore('base', {
state: (): BaseState => {
return DefaultBaseState()
state: (): BaseState => {
return DefaultBaseState()
},
getters: {
collect(): Dict {
return this.myDictList[0]
},
getters: {
collect(): Dict {
return this.myDictList[0]
},
simple(): Dict {
return this.myDictList[1]
},
wrong(): Dict {
return this.myDictList[2]
},
skipWordNames() {
return this.simple.originWords.map(v => v.name.toLowerCase())
},
skipWordNamesWithSimpleWords() {
return this.simple.originWords.map(v => v.name.toLowerCase()).concat(this.simpleWords)
},
isArticle(state: BaseState): boolean {
//如果是收藏时,特殊判断
if (this.currentDict.type === DictType.collect) {
return state.current.practiceType === DictType.article
}
return [
DictType.article,
].includes(this.currentDict.type)
},
currentDict(): Dict {
return this.myDictList[this.current.index]
},
chapter(state: BaseState): Word[] {
return this.currentDict.chapterWords[this.currentDict.chapterIndex] ?? []
},
chapterName(state: BaseState) {
let title = ''
switch (this.currentDict.type) {
case DictType.collect:
if (state.current.practiceType === DictType.article) {
return `${this.currentDict.chapterIndex + 1}`
}
case DictType.wrong:
case DictType.simple:
return this.currentDict.name
case DictType.word:
return `${this.currentDict.chapterIndex + 1}`
}
return title
simple(): Dict {
return this.myDictList[1]
},
wrong(): Dict {
return this.myDictList[2]
},
skipWordNames() {
return this.simple.originWords.map(v => v.name.toLowerCase())
},
skipWordNamesWithSimpleWords() {
return this.simple.originWords.map(v => v.name.toLowerCase()).concat(this.simpleWords)
},
isArticle(state: BaseState): boolean {
//如果是收藏时,特殊判断
if (this.currentDict.type === DictType.collect) {
return state.current.practiceType === DictType.article
}
return [
DictType.article,
].includes(this.currentDict.type)
},
currentDict(): Dict {
return this.myDictList[this.current.index]
},
chapter(state: BaseState): Word[] {
return this.currentDict.chapterWords[this.currentDict.chapterIndex] ?? []
},
chapterName(state: BaseState) {
let title = ''
switch (this.currentDict.type) {
case DictType.collect:
if (state.current.practiceType === DictType.article) {
return `${this.currentDict.chapterIndex + 1}`
}
case DictType.wrong:
case DictType.simple:
return this.currentDict.name
case DictType.word:
return `${this.currentDict.chapterIndex + 1}`
}
return title
}
},
actions: {
setState(obj: any) {
//这样不会丢失watch的值的引用
merge(this, obj)
},
async init(outData?: any) {
return new Promise(async resolve => {
try {
if (outData) {
this.setState(outData)
} else {
let configStr: string = await localforage.getItem(SAVE_DICT_KEY.key)
let data = checkAndUpgradeSaveDict(configStr)
this.setState(data)
}
localforage.setItem(SAVE_DICT_KEY.key, JSON.stringify({val: this.$state, version: SAVE_DICT_KEY.version}))
} catch (e) {
console.error('读取本地dict数据失败', e)
}
},
actions: {
setState(obj: any) {
//这样不会丢失watch的值的引用
merge(this, obj)
},
async init(outData?: any) {
return new Promise(async resolve => {
try {
if (outData) {
this.setState(outData)
} else {
let configStr: string = await localforage.getItem(SAVE_DICT_KEY.key)
let data = checkAndUpgradeSaveDict(configStr)
this.setState(data)
}
localforage.setItem(SAVE_DICT_KEY.key, JSON.stringify({val: this.$state, version: SAVE_DICT_KEY.version}))
} catch (e) {
console.error('读取本地dict数据失败', e)
}
const runtimeStore = useRuntimeStore()
const runtimeStore = useRuntimeStore()
if (location.href.includes('?mode=article')) {
console.log('文章')
let dict = {
...cloneDeep(DefaultDict),
id: 'article_nce2',
name: "新概念英语2-课文",
description: '新概念英语2-课文',
category: '英语学习',
tags: ['新概念英语'],
url: 'NCE_2.json',
translateLanguage: 'common',
language: 'en',
type: DictType.article,
resourceId: 'article_nce2',
length: 96
}
let rIndex = this.myDictList.findIndex((v: Dict) => v.id === dict.id)
if (rIndex > -1) {
this.myDictList[rIndex] = dict
this.current.index = rIndex
} else {
this.myDictList.push(cloneDeep(dict))
this.current.index = this.myDictList.length - 1
}
}
if (this.current.index < 3) {
store.currentDict.words = cloneDeep(n)
store.currentDict.chapterWords = [store.currentDict.words]
} else {
//自定义的词典文章只删除了sections单词并未做删除所以这里不需要处理
if (this.currentDict.isCustom) {
} else {
//处理非自定义的情况。
let dictResourceUrl = `./dicts/${this.currentDict.language}/${this.currentDict.type}/${this.currentDict.translateLanguage}/${this.currentDict.url}`;
if ([DictType.word].includes(this.currentDict.type)) {
if (!this.currentDict.originWords.length) {
let r = await fetch(dictResourceUrl)
let v = await r.json()
v.map(s => {
s.id = nanoid(6)
})
if (this.currentDict.translateLanguage === 'common') {
let r2 = await fetch('./translate/en2zh_CN-min.json')
// fetch('http://sc.ttentau.top/en2zh_CN-min.json').then(r2 => {
let list: Word[] = await r2.json()
if (list && list.length) {
runtimeStore.translateWordList = list
}
}
this.currentDict.originWords = cloneDeep(v)
this.currentDict.words = cloneDeep(v)
this.currentDict.chapterWords = chunk(this.currentDict.words, this.currentDict.chapterWordNumber)
}
}
if ([DictType.article].includes(this.currentDict.type)) {
if (!this.currentDict.articles.length) {
let r = await fetch(dictResourceUrl)
let s: any[] = await r.json()
this.currentDict.articles = cloneDeep(s.map(v => {
v.id = nanoid(6)
return v
}))
}
}
}
}
//TODO 先这样,默认加载
if (!runtimeStore.translateWordList.length) {
setTimeout(async () => {
let r2 = await fetch('./translate/en2zh_CN-min.json')
// fetch('http://sc.ttentau.top/en2zh_CN-min.json').then(r2 => {
let list: Word[] = await r2.json()
if (list && list.length) {
runtimeStore.translateWordList = list
}
})
}
emitter.emit(EventKey.changeDict)
resolve(true)
})
},
saveStatistics(statistics: DisplayStatistics) {
if (statistics.spend > 1000 * 10) {
delete statistics.wrongWords
this.currentDict.statistics.push(statistics)
}
},
async changeDict(dict: Dict, practiceType?: DictType, chapterIndex?: number, wordIndex?: number) {
//TODO 保存统计
// this.saveStatistics()
console.log('changeDict', cloneDeep(dict), chapterIndex, wordIndex)
if (chapterIndex === undefined) chapterIndex = dict.chapterIndex
if (wordIndex === undefined) wordIndex = dict.wordIndex
if (practiceType === undefined) this.current.practiceType = practiceType
if ([DictType.collect,
DictType.simple,
DictType.wrong].includes(dict.type)) {
dict.chapterIndex = 0
dict.wordIndex = wordIndex
dict.chapterWordNumber = dict.words.length
dict.chapterWords = [dict.words]
} else {
if (dict.type === DictType.article) {
if (chapterIndex > dict.articles.length) {
dict.chapterIndex = 0
dict.wordIndex = 0
}
} else {
if (chapterIndex > dict.chapterWords.length) {
dict.chapterIndex = 0
dict.wordIndex = 0
}
}
}
// await checkDictHasTranslate(dict)
let rIndex = this.myDictList.findIndex((v: Dict) => v.id === dict.id)
if (rIndex > -1) {
this.myDictList[rIndex] = dict
this.current.index = rIndex
} else {
this.myDictList.push(cloneDeep(dict))
this.current.index = this.myDictList.length - 1
}
emitter.emit(EventKey.changeDict)
if (location.href.includes('?mode=article')) {
console.log('文章')
let dict = {
...cloneDeep(DefaultDict),
id: 'article_nce2',
name: "新概念英语2-课文",
description: '新概念英语2-课文',
category: '英语学习',
tags: ['新概念英语'],
url: 'NCE_2.json',
translateLanguage: 'common',
language: 'en',
type: DictType.article,
resourceId: 'article_nce2',
length: 96
}
let rIndex = this.myDictList.findIndex((v: Dict) => v.id === dict.id)
if (rIndex > -1) {
this.myDictList[rIndex] = dict
this.current.index = rIndex
} else {
this.myDictList.push(cloneDeep(dict))
this.current.index = this.myDictList.length - 1
}
}
if (this.current.index < 3) {
// this.currentDict.words = cloneDeep(n)
// this.currentDict.chapterWords = [this.currentDict.words]
} else {
//自定义的词典文章只删除了sections单词并未做删除所以这里不需要处理
if (this.currentDict.isCustom) {
} else {
//处理非自定义的情况。
let dictResourceUrl = `./dicts/${this.currentDict.language}/${this.currentDict.type}/${this.currentDict.translateLanguage}/${this.currentDict.url}`;
if ([DictType.word].includes(this.currentDict.type)) {
if (!this.currentDict.originWords.length) {
let r = await fetch(dictResourceUrl)
let v = await r.json()
v.map(s => {
s.id = nanoid(6)
})
if (this.currentDict.translateLanguage === 'common') {
let r2 = await fetch('./translate/en2zh_CN-min.json')
// fetch('http://sc.ttentau.top/en2zh_CN-min.json').then(r2 => {
let list: Word[] = await r2.json()
if (list && list.length) {
runtimeStore.translateWordList = list
}
}
this.currentDict.originWords = cloneDeep(v)
this.currentDict.words = cloneDeep(v)
this.currentDict.chapterWords = chunk(this.currentDict.words, this.currentDict.chapterWordNumber)
}
}
if ([DictType.article].includes(this.currentDict.type)) {
if (!this.currentDict.articles.length) {
let r = await fetch(dictResourceUrl)
let s: any[] = await r.json()
this.currentDict.articles = cloneDeep(s.map(v => {
v.id = nanoid(6)
return v
}))
}
}
}
}
//TODO 先这样,默认加载
if (!runtimeStore.translateWordList.length) {
setTimeout(async () => {
let r2 = await fetch('./translate/en2zh_CN-min.json')
// fetch('http://sc.ttentau.top/en2zh_CN-min.json').then(r2 => {
let list: Word[] = await r2.json()
if (list && list.length) {
runtimeStore.translateWordList = list
}
})
}
emitter.emit(EventKey.changeDict)
resolve(true)
})
},
saveStatistics(statistics: DisplayStatistics) {
if (statistics.spend > 1000 * 10) {
delete statistics.wrongWords
this.currentDict.statistics.push(statistics)
}
},
async changeDict(dict: Dict, practiceType?: DictType, chapterIndex?: number, wordIndex?: number) {
//TODO 保存统计
// this.saveStatistics()
console.log('changeDict', cloneDeep(dict), chapterIndex, wordIndex)
if (chapterIndex === undefined) chapterIndex = dict.chapterIndex
if (wordIndex === undefined) wordIndex = dict.wordIndex
if (practiceType === undefined) this.current.practiceType = practiceType
if ([DictType.collect,
DictType.simple,
DictType.wrong].includes(dict.type)) {
dict.chapterIndex = 0
dict.wordIndex = wordIndex
dict.chapterWordNumber = dict.words.length
dict.chapterWords = [dict.words]
} else {
if (dict.type === DictType.article) {
if (chapterIndex > dict.articles.length) {
dict.chapterIndex = 0
dict.wordIndex = 0
}
} else {
if (chapterIndex > dict.chapterWords.length) {
dict.chapterIndex = 0
dict.wordIndex = 0
}
}
}
// await checkDictHasTranslate(dict)
let rIndex = this.myDictList.findIndex((v: Dict) => v.id === dict.id)
if (rIndex > -1) {
this.myDictList[rIndex] = dict
this.current.index = rIndex
} else {
this.myDictList.push(cloneDeep(dict))
this.current.index = this.myDictList.length - 1
}
emitter.emit(EventKey.changeDict)
}
},
})

View File

@@ -1,108 +1,114 @@
import {defineStore} from "pinia"
import {cloneDeep, merge} from "lodash-es";
import {DefaultShortcutKeyMap} from "@/types.ts";
import {SAVE_SETTING_KEY} from "@/utils/const.ts";
import {checkAndUpgradeSaveDict, checkAndUpgradeSaveSetting} from "@/utils";
import { defineStore } from "pinia"
import { cloneDeep, merge } from "lodash-es";
import { DefaultShortcutKeyMap } from "@/types.ts";
import { SAVE_SETTING_KEY } from "@/utils/const.ts";
import { checkAndUpgradeSaveDict, checkAndUpgradeSaveSetting } from "@/utils";
export interface SettingState {
showToolbar: boolean,
show: boolean,
showToolbar: boolean,
show: boolean,
allSound: boolean,
wordSound: boolean,
wordSoundVolume: number,
wordSoundSpeed: number,
wordSoundType: string,
keyboardSound: boolean,
keyboardSoundVolume: number,
keyboardSoundFile: string,
translateSound: boolean,
translateSoundVolume: number,
effectSound: boolean,
effectSoundVolume: number,
repeatCount: number,
repeatCustomCount?: number,
dictation: boolean,
translate: boolean,
showNearWord: boolean
ignoreCase: boolean
allowWordTip: boolean
waitTimeForChangeWord: number
fontSize: {
articleForeignFontSize: number,
articleTranslateFontSize: number,
wordForeignFontSize: number,
wordTranslateFontSize: number,
},
showPanel: boolean,
theme: string,
collapse: boolean,
chapterWordNumber: number,
shortcutKeyMap: Record<string, string>,
first: boolean
load: boolean
allSound: boolean,
wordSound: boolean,
wordSoundVolume: number,
wordSoundSpeed: number,
wordSoundType: string,
keyboardSound: boolean,
keyboardSoundVolume: number,
keyboardSoundFile: string,
translateSound: boolean,
translateSoundVolume: number,
effectSound: boolean,
effectSoundVolume: number,
repeatCount: number,
repeatCustomCount?: number,
dictation: boolean,
translate: boolean,
showNearWord: boolean
ignoreCase: boolean
allowWordTip: boolean
waitTimeForChangeWord: number
autoNext: boolean
dictationShowWordLength: boolean//默写时显示单词长度,即用下划线 _ 来显示每个字符
fontSize: {
articleForeignFontSize: number,
articleTranslateFontSize: number,
wordForeignFontSize: number,
wordTranslateFontSize: number,
},
showPanel: boolean,
theme: string,
collapse: boolean,
chapterWordNumber: number,
shortcutKeyMap: Record<string, string>,
first: boolean
load: boolean
}
export const DefaultSettingState = (): SettingState => ({
showToolbar: true,
show: false,
showPanel: true,
showToolbar: true,
show: false,
showPanel: true,
allSound: true,
wordSound: true,
wordSoundVolume: 100,
wordSoundSpeed: 1,
wordSoundType: 'us',
keyboardSound: true,
keyboardSoundVolume: 100,
keyboardSoundFile: '机械键盘2',
translateSound: true,
translateSoundVolume: 100,
effectSound: true,
effectSoundVolume: 100,
repeatCount: 1,
repeatCustomCount: null,
dictation: false,
translate: true,
allSound: true,
wordSound: true,
wordSoundVolume: 100,
wordSoundSpeed: 1,
wordSoundType: 'us',
keyboardSound: true,
keyboardSoundVolume: 100,
keyboardSoundFile: '机械键盘2',
translateSound: true,
translateSoundVolume: 100,
effectSound: true,
effectSoundVolume: 100,
repeatCount: 1,
repeatCustomCount: null,
dictation: false,
translate: true,
showNearWord: true,
ignoreCase: true,
allowWordTip: true,
fontSize: {
articleForeignFontSize: 48,
articleTranslateFontSize: 20,
wordForeignFontSize: 48,
wordTranslateFontSize: 20,
},
waitTimeForChangeWord: 300,
theme: 'auto',
collapse: false,
chapterWordNumber: DefaultChapterWordNumber,
shortcutKeyMap: cloneDeep(DefaultShortcutKeyMap),
first: true,
load: false
showNearWord: true,
ignoreCase: true,
allowWordTip: true,
fontSize: {
articleForeignFontSize: 48,
articleTranslateFontSize: 20,
wordForeignFontSize: 48,
wordTranslateFontSize: 20,
},
waitTimeForChangeWord: 300,
autoNext: true,
dictationShowWordLength: true,
theme: 'auto',
collapse: false,
chapterWordNumber: DefaultChapterWordNumber,
shortcutKeyMap: cloneDeep(DefaultShortcutKeyMap),
first: true,
load: false
})
export const DefaultChapterWordNumber = 30
export const useSettingStore = defineStore('setting', {
state: (): SettingState => {
return DefaultSettingState()
state: (): SettingState => {
return DefaultSettingState()
},
actions: {
setState(obj: any) {
//这样不会丢失watch的值的引用
merge(this, obj)
},
actions: {
setState(obj: any) {
//这样不会丢失watch的值的引用
merge(this, obj)
},
init() {
return new Promise(resolve => {
let configStr = localStorage.getItem(SAVE_SETTING_KEY.key)
if (!configStr) configStr = localStorage.getItem(SAVE_SETTING_KEY.oldKey)
let data = checkAndUpgradeSaveSetting(configStr)
this.setState(data)
localStorage.setItem(SAVE_SETTING_KEY.key, JSON.stringify({val: this.$state, version: SAVE_SETTING_KEY.version}))
this.load = true
resolve(true)
})
}
init() {
return new Promise(resolve => {
let configStr = localStorage.getItem(SAVE_SETTING_KEY.key)
if (!configStr) configStr = localStorage.getItem(SAVE_SETTING_KEY.oldKey)
let data = checkAndUpgradeSaveSetting(configStr)
this.setState(data)
localStorage.setItem(SAVE_SETTING_KEY.key, JSON.stringify({
val: this.$state,
version: SAVE_SETTING_KEY.version
}))
this.load = true
resolve(true)
})
}
}
})