This commit is contained in:
zyronon
2023-09-25 18:43:22 +08:00
parent 3d72b9de08
commit 5c1994f0db
12 changed files with 719 additions and 439 deletions

3
components.d.ts vendored
View File

@@ -16,6 +16,7 @@ declare module 'vue' {
BatchAddArticle: typeof import('./src/components/Add/BatchAddArticle.vue')['default']
ChapterDetail: typeof import('./src/components/ChapterDetail.vue')['default']
ChapterList: typeof import('./src/components/ChapterList.vue')['default']
Close: typeof import('./src/components/Close.vue')['default']
DictList: typeof import('./src/components/DictList.vue')['default']
DictModal: typeof import('./src/components/Toolbar/DictModal.vue')['default']
EditAbleText: typeof import('./src/components/EditAbleText.vue')['default']
@@ -29,6 +30,8 @@ declare module 'vue' {
Fireworks: typeof import('./src/components/Fireworks.vue')['default']
Footer: typeof import('./src/components/Practice/Footer.vue')['default']
IconWrapper: typeof import('./src/components/IconWrapper.vue')['default']
Input: typeof import('./src/components/Input.vue')['default']
List: typeof import('./src/components/List.vue')['default']
MiniModal: typeof import('./src/components/MiniModal.vue')['default']
Modal: typeof import('./src/components/Modal/Modal.vue')['default']
PopConfirm: typeof import('./src/components/PopConfirm.vue')['default']

View File

@@ -16,6 +16,7 @@
"dependencies": {
"@opentranslate/baidu": "^1.4.2",
"@opentranslate/translator": "^1.4.2",
"@types/uuid": "^9.0.4",
"axios": "^1.5.0",
"copy-to-clipboard": "^3.3.3",
"element-plus": "^2.3.9",
@@ -26,6 +27,7 @@
"pinia": "^2.1.6",
"swiper": "^10.1.0",
"tesseract.js": "^4.1.1",
"uuid": "^9.0.1",
"vue": "^3.3.4"
},
"devDependencies": {

15
pnpm-lock.yaml generated
View File

@@ -11,6 +11,9 @@ dependencies:
'@opentranslate/translator':
specifier: ^1.4.2
version: 1.4.2
'@types/uuid':
specifier: ^9.0.4
version: 9.0.4
axios:
specifier: ^1.5.0
version: 1.5.0
@@ -41,6 +44,9 @@ dependencies:
tesseract.js:
specifier: ^4.1.1
version: 4.1.1
uuid:
specifier: ^9.0.1
version: 9.0.1
vue:
specifier: ^3.3.4
version: 3.3.4
@@ -875,6 +881,10 @@ packages:
resolution: {integrity: sha512-u95svzDlTysU5xecFNTgfFG5RUWu1A9P0VzgpcIiGZA9iraHOdSzcxMxQ55DyeRaGCSxQi7LxXDI4rzq/MYfdg==}
dev: false
/@types/uuid@9.0.4:
resolution: {integrity: sha512-zAuJWQflfx6dYJM62vna+Sn5aeSWhh3OB+wfUEACNcqUSc0AGc5JKl+ycL1vrH7frGTXhJchYjE1Hak8L819dA==}
dev: false
/@types/web-bluetooth@0.0.16:
resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==}
dev: false
@@ -2985,6 +2995,11 @@ packages:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
dev: true
/uuid@9.0.1:
resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
hasBin: true
dev: false
/v8-compile-cache-lib@3.0.1:
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
requiresBuild: true

View File

@@ -18,7 +18,9 @@ import {useDisableEventListener} from "@/hooks/event.ts";
import {MessageBox} from "@/utils/MessageBox.tsx";
import BaseIcon from "@/components/BaseIcon.vue";
import {useBaseStore} from "@/stores/base.ts";
import {$ref} from "vue/macros";
import {$computed, $ref} from "vue/macros";
import Input from "@/components/Input.vue";
import List from "@/components/List.vue";
interface IProps {
selectIndex?: number
@@ -31,7 +33,9 @@ const props = withDefaults(defineProps<IProps>(), {
const base = useBaseStore()
let article = $ref<Article>(cloneDeep(DefaultArticle))
let selectIndex = $ref<number>(props.selectIndex)
let selectItem = $ref<number>(props.selectIndex)
let networkTranslateEngine = $ref('baidu')
let searchKey = $ref('')
let progress = $ref(0)
const TranslateEngineOptions = [
{value: 'baidu', label: '百度'},
@@ -264,26 +268,43 @@ watch(() => article.useTranslateType, () => {
}
})
const list = $computed(() => {
if (searchKey) {
return base.currentEditDict.articles.filter((item: Article) => {
//把搜索内容,分词之后,判断是否有这个词,比单纯遍历包含体验更好
return searchKey.toLowerCase().split(' ').filter(v => v).some(value => {
return item.title.toLowerCase().includes(value) || item.titleTranslate.toLowerCase().includes(value)
})
})
} else {
return base.currentEditDict.articles
}
})
function selectArticle(index: number) {
article = cloneDeep(base.currentEditDict.articles[index])
selectIndex = index
}
function delArticle(index: number) {
if (index < selectIndex) {
selectIndex--
} else if (index === selectIndex) {
if (selectIndex === base.currentEditDict.articles.length - 1) {
function delArticle(item: Article) {
let rIndex = base.currentEditDict.articles.findIndex((v: Article) => v.id === item.id)
if (rIndex > -1) {
if (index < selectIndex) {
selectIndex--
} else if (index === selectIndex) {
if (selectIndex === base.currentEditDict.articles.length - 1) {
selectIndex--
}
}
base.currentEditDict.articles.splice(index, 1)
if (selectIndex < 0) {
article = cloneDeep(DefaultArticle)
} else {
article = cloneDeep(base.currentEditDict.articles[selectIndex])
}
}
base.currentEditDict.articles.splice(index, 1)
if (selectIndex < 0) {
article = cloneDeep(DefaultArticle)
} else {
article = cloneDeep(base.currentEditDict.articles[selectIndex])
}
}
</script>
<template>
@@ -293,27 +314,19 @@ function delArticle(index: number) {
<div class="dict-name">{{ base.currentEditDict.name }}</div>
<BaseIcon title="选择其他词典/文章" icon="carbon:change-catalog"/>
</header>
<div class="article-list">
<div class="item"
:class="[
(selectIndex === index) && 'active'
]"
@click="selectArticle(index)"
v-for="(item,index) in base.currentEditDict.articles">
<div class="left">
<div class="name"> {{ `${index + 1}. ${item.title}` }}</div>
<div class="translate-name"> {{ ` ${item.titleTranslate}` }}</div>
</div>
<div class="right">
<BaseIcon
@click="delArticle(index)"
title="删除" icon="fluent:delete-24-regular"/>
<BaseIcon
@click="delArticle(index)"
title="删除" icon="carbon:move"/>
</div>
</div>
</div>
<List
v-model:list="list"
v-model:searchKey="searchKey"
:select-index="selectIndex"
:row-key="(item:Article) => item.title"
@del-article="delArticle"
@select-article="selectArticle"
>
<template v-slot="{item,index}">
<div class="name"> {{ `${index + 1}. ${item.title}` }}</div>
<div class="translate-name"> {{ ` ${item.titleTranslate}` }}</div>
</template>
</List>
<div class="footer">
<BaseButton>导入</BaseButton>
<BaseButton>导出</BaseButton>
@@ -471,6 +484,7 @@ function delArticle(index: number) {
display: flex;
justify-content: space-between;
align-items: center;
//opacity: 0;
.dict-name {
font-size: 30rem;
@@ -478,46 +492,12 @@ function delArticle(index: number) {
}
}
.article-list {
flex: 1;
width: 300rem;
overflow: auto;
.name {
font-size: 18rem;
}
.item {
background: #e1e1e1;
border-radius: 8rem;
margin-bottom: 10rem;
padding: 10rem;
display: flex;
justify-content: space-between;
transition: all .3s;
.right {
display: flex;
flex-direction: column;
transition: all .3s;
opacity: 0;
}
&:hover {
.right {
opacity: 1;
}
}
&.active {
background: var(--color-item-active);
color: white;
}
.name {
font-size: 18rem;
}
.translate-name {
font-size: 16rem;
}
}
.translate-name {
font-size: 16rem;
}
.footer {
@@ -534,6 +514,7 @@ function delArticle(index: number) {
display: flex;
gap: $space;
padding: $space;
//opacity: 0;
}
.row {

View File

@@ -5,7 +5,7 @@ import IconWrapper from "@/components/IconWrapper.vue";
import {Icon} from "@iconify/vue";
defineProps<{
title: string,
title?: string,
icon: string,
}>()

22
src/components/Close.vue Normal file
View File

@@ -0,0 +1,22 @@
<script setup lang="ts">
import {Icon} from "@iconify/vue";
defineEmits(['click'])
</script>
<template>
<div class="close"
@click="$emit('click')"
>
<Icon icon="ic:round-close"
width="20"
/>
</div>
</template>
<style scoped lang="scss">
.close {
cursor: pointer;
}
</style>

77
src/components/Input.vue Normal file
View File

@@ -0,0 +1,77 @@
<script setup lang="ts">
import {$ref} from "vue/macros";
import {Icon} from "@iconify/vue";
import Close from "@/components/Close.vue";
import {useWindowClick} from "@/hooks/event.ts";
defineProps<{
modelValue: string
}>()
defineEmits(['update:modelValue'])
let focus = $ref(false)
let inputEl = $ref<HTMLDivElement>()
useWindowClick((e: PointerEvent) => {
focus = inputEl.contains(e.target as any);
})
</script>
<template>
<div class="base-input"
:class="{focus}"
ref="inputEl"
>
<Icon icon="fluent:search-24-regular"
width="20"/>
<input type="text"
:value="modelValue"
@input="e=>$emit('update:modelValue',e.target.value)"
>
<Close @click="$emit('update:modelValue','')"/>
</div>
</template>
<style scoped lang="scss">
@import "@/assets/css/style.scss";
.base-input {
border: 1px solid var(--color-main-bg);
border-radius: 4rem;
padding: 3rem 5rem;
transition: all .3s;
display: flex;
align-items: center;
transition: all .3s;
:deep(svg) {
transition: all .3s;
color: var(--color-main-bg);
}
&.focus {
border: 1px solid var(--color-main-active);
:deep(svg) {
color: gray;
}
}
input {
font-family: $font-family;
font-size: 18rem;
outline: none;
min-height: 20rem;
flex: 1;
box-sizing: border-box;
outline: none;
border: none;
&[readonly] {
cursor: not-allowed;
opacity: .7;
}
}
}
</style>

171
src/components/List.vue Normal file
View File

@@ -0,0 +1,171 @@
<script setup lang="ts" generic="T">
import BaseIcon from "@/components/BaseIcon.vue";
import Input from "@/components/Input.vue";
import {$ref} from "vue/macros";
import {cloneDeep, throttle} from "lodash-es";
let dragIndex = $ref(-1)
interface IProps {
list: T[]
searchKey: string,
selectIndex: number,
rowKey: (item: T) => string
}
const props = defineProps<IProps>()
const emit = defineEmits<{
selectArticle: [index: number],
delArticle: [item: T],
'update:searchKey': [val: string],
'update:list': [list: T[]],
}>()
let draggable = $ref(false)
function dragstart(index: number) {
dragIndex = index;
}
const dragenter = throttle((e, index) => {
// console.log('dragenter', 'dragIndex', dragIndex, 'index', index)
e.preventDefault();
// 避免源对象触发自身的dragenter事件
if (dragIndex !== index && dragIndex !== -1) {
const source = props.list[dragIndex];
let temp = cloneDeep(props.list)
temp.splice(dragIndex, 1);
temp.splice(index, 0, source);
emit('update:list', temp)
// props.list = temp
// 排序变化后目标对象的索引变成源对象的索引
dragIndex = index;
}
}, 200)
function dragover(e, index) {
// console.log('dragover')
e.preventDefault();
}
function dragend() {
// console.log('dragend')
draggable = false
dragIndex = -1
}
</script>
<template>
<div class="list-wrapper">
<div class="search">
<Input :model-value="searchKey"
@update:model-value="(e:string) => emit('update:searchKey',e)"
/>
</div>
<transition-group
name="drag"
class="list"
tag="div"
>
<div class="item"
:class="[
(props.selectIndex === index) && 'active',
draggable && 'draggable',
(dragIndex === index) && 'active'
]"
@click="emit('selectArticle',index)"
v-for="(item,index) in props.list"
:key="rowKey(item)"
:draggable="draggable"
@dragstart="dragstart(index)"
@dragenter="dragenter($event, index)"
@dragover="dragover($event, index)"
@dragend="dragend()"
>
<div class="left">
<slot :item="item" :index="index"></slot>
</div>
<div class="right">
<BaseIcon
@click="emit('delArticle',item)"
title="删除" icon="fluent:delete-24-regular"/>
<div
@mousedown="draggable = true"
@mouseup="draggable = false"
>
<BaseIcon
icon="carbon:move"/>
</div>
</div>
</div>
</transition-group>
</div>
</template>
<style scoped lang="scss">
.drag-move, /* 对移动中的元素应用的过渡 */
.drag-enter-active,
.drag-leave-active {
transition: all 0.5s ease;
}
.drag-enter-from,
.drag-leave-to {
opacity: 0;
transform: translateX(30px);
}
/* 确保将离开的元素从布局流中删除
以便能够正确地计算移动的动画。 */
.drag-leave-active {
position: absolute;
}
.list-wrapper {
flex: 1;
overflow: auto;
padding-right: 5rem;
.search {
margin: 10rem 0;
width: 260rem;
}
.list {
.item {
width: 260rem;
box-sizing: border-box;
background: #e1e1e1;
border-radius: 8rem;
margin-bottom: 10rem;
padding: 10rem;
display: flex;
justify-content: space-between;
transition: all .3s;
.right {
display: flex;
flex-direction: column;
transition: all .3s;
opacity: 0;
}
&:hover {
.right {
opacity: 1;
}
}
&.active {
background: var(--color-item-active);
color: white;
}
&.draggable {
cursor: move;
}
}
}
}
</style>

View File

@@ -25,6 +25,7 @@ export default {
methods: {
showPop(e) {
if (this.disabled) return
if (!this.title) return
e.stopPropagation()
let rect = e.target.getBoundingClientRect()
this.show = true
@@ -49,7 +50,7 @@ export default {
<Teleport to="body">
<Transition name="fade">
{
this.show && (
this.show && this.title && (
<div ref="tip" class="tip">
{this.title}
</div>

View File

@@ -3,7 +3,7 @@ import {emitter, EventKey} from "@/utils/eventBus.ts";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {$ref} from "vue/macros";
export function useWindowClick(cb: () => void) {
export function useWindowClick(cb: (e: PointerEvent) => void) {
onMounted(() => {
emitter.on(EventKey.closeOther, cb)
window.addEventListener('click', cb)

View File

@@ -2,257 +2,261 @@ import {defineStore} from 'pinia'
import {Dict, DictType, SaveDictKey, Sort, Statistics, Word} from "../types.ts"
import {chunk, cloneDeep} from "lodash-es";
import {emitter, EventKey} from "@/utils/eventBus.ts"
import {v4 as uuidv4} from 'uuid';
export interface State {
newWordDict: Dict,
skipWordDict: Dict,
wrongWordDict: Dict,
dict: Dict,
myDicts: Dict[],
current: {
dictType: DictType,
index: number,
editIndex: number,
repeatNumber: number,
},
simpleWords: string[],
sideIsOpen: boolean,
load: boolean
newWordDict: Dict,
skipWordDict: Dict,
wrongWordDict: Dict,
dict: Dict,
myDicts: Dict[],
current: {
dictType: DictType,
index: number,
editIndex: number,
repeatNumber: number,
},
simpleWords: string[],
sideIsOpen: boolean,
load: boolean
}
export const useBaseStore = defineStore('base', {
state: (): State => {
return {
newWordDict: {
name: '生词本',
sort: Sort.normal,
type: DictType.newDict,
originWords: [],
articles: [],
words: [],
chapterWordNumber: 15,
chapterWords: [],
chapterIndex: 0,
chapterWordIndex: 0,
statistics: [],
url: '',
},
skipWordDict: {
name: '简单词',
sort: Sort.normal,
type: DictType.skipDict,
originWords: [],
articles: [],
words: [],
chapterWordNumber: 15,
chapterWords: [],
chapterIndex: 0,
chapterWordIndex: 0,
statistics: [],
url: '',
},
wrongWordDict: {
name: '错词本',
sort: Sort.normal,
type: DictType.wrongDict,
originWords: [],
articles: [],
words: [],
chapterWordNumber: 15,
chapterWords: [],
chapterIndex: 0,
chapterWordIndex: 0,
statistics: [],
url: '',
},
// dict: {
// name: '新概念英语-2',
// sort: Sort.normal,
// type: DictType.innerDict,
// originWords: [],
// articles: [],
// words: [],
// chapterWordNumber: 15,
// chapterWords: [],
// chapterIndex: 0,
// chapterWordIndex: 0,
// statistics: [],
// url: '/dicts/NCE_2.json',
// },
dict: {
name: '新概念英语-2',
sort: Sort.normal,
type: DictType.publicArticle,
originWords: [],
articles: [],
words: [],
chapterWordNumber: 15,
chapterWords: [],
chapterIndex: 0,
chapterWordIndex: 0,
statistics: [],
url: '/articles/NCE_2.json',
},
myDicts: [
{
name: '新概念英语-2',
sort: Sort.normal,
type: DictType.publicArticle,
originWords: [],
articles: [],
words: [],
chapterWordNumber: 15,
chapterWords: [],
chapterIndex: 0,
chapterWordIndex: 0,
statistics: [],
url: '/articles/NCE_2.json',
state: (): State => {
return {
newWordDict: {
name: '生词本',
sort: Sort.normal,
type: DictType.newDict,
originWords: [],
articles: [],
words: [],
chapterWordNumber: 15,
chapterWords: [],
chapterIndex: 0,
chapterWordIndex: 0,
statistics: [],
url: '',
},
skipWordDict: {
name: '简单词',
sort: Sort.normal,
type: DictType.skipDict,
originWords: [],
articles: [],
words: [],
chapterWordNumber: 15,
chapterWords: [],
chapterIndex: 0,
chapterWordIndex: 0,
statistics: [],
url: '',
},
wrongWordDict: {
name: '错词本',
sort: Sort.normal,
type: DictType.wrongDict,
originWords: [],
articles: [],
words: [],
chapterWordNumber: 15,
chapterWords: [],
chapterIndex: 0,
chapterWordIndex: 0,
statistics: [],
url: '',
},
// dict: {
// name: '新概念英语-2',
// sort: Sort.normal,
// type: DictType.innerDict,
// originWords: [],
// articles: [],
// words: [],
// chapterWordNumber: 15,
// chapterWords: [],
// chapterIndex: 0,
// chapterWordIndex: 0,
// statistics: [],
// url: '/dicts/NCE_2.json',
// },
dict: {
name: '新概念英语-2',
sort: Sort.normal,
type: DictType.publicArticle,
originWords: [],
articles: [],
words: [],
chapterWordNumber: 15,
chapterWords: [],
chapterIndex: 0,
chapterWordIndex: 0,
statistics: [],
url: '/articles/NCE_2.json',
},
myDicts: [
{
name: '新概念英语-2',
sort: Sort.normal,
type: DictType.publicArticle,
originWords: [],
articles: [],
words: [],
chapterWordNumber: 15,
chapterWords: [],
chapterIndex: 0,
chapterWordIndex: 0,
statistics: [],
url: '/articles/NCE_2.json',
}
],
current: {
dictType: DictType.publicArticle,
index: 0,
editIndex: 0,
repeatNumber: 0,
},
sideIsOpen: false,
simpleWords: [
'a', 'an', 'of', 'and',
'i', 'my', 'you', 'your',
'me', 'am', 'is', 'do', 'are',
'what', 'who', 'where', 'how', 'no', 'yes',
'not', 'did', 'were', 'can', 'could', 'it',
'the', 'to'
],
load: false
}
],
current: {
dictType: DictType.publicArticle,
index: 0,
editIndex: 0,
repeatNumber: 0,
},
sideIsOpen: false,
simpleWords: [
'a', 'an', 'of', 'and',
'i', 'my', 'you', 'your',
'me', 'am', 'is', 'do', 'are',
'what', 'who', 'where', 'how', 'no', 'yes',
'not', 'did', 'were', 'can', 'could', 'it',
'the', 'to'
],
load: false
}
},
getters: {
skipWordNames: (state: State) => {
return state.skipWordDict.originWords.map(v => v.name.toLowerCase())
},
skipWordNamesWithSimpleWords: (state: State) => {
return state.skipWordDict.originWords.map(v => v.name.toLowerCase()).concat(state.simpleWords)
getters: {
skipWordNames: (state: State) => {
return state.skipWordDict.originWords.map(v => v.name.toLowerCase())
},
skipWordNamesWithSimpleWords: (state: State) => {
return state.skipWordDict.originWords.map(v => v.name.toLowerCase()).concat(state.simpleWords)
},
isArticle(state: State): boolean {
return [
DictType.publicArticle,
DictType.customArticle
].includes(state.current.dictType)
},
currentDict(state: State): Dict {
switch (state.current.dictType) {
case DictType.newDict:
return state.newWordDict
case DictType.skipDict:
return state.skipWordDict
case DictType.wrongDict:
return state.wrongWordDict
case DictType.publicDict:
case DictType.publicArticle:
case DictType.customDict:
case DictType.customArticle:
return this.myDicts[this.current.index]
}
},
currentEditDict(): Dict {
return this.myDicts[this.current.editIndex]
},
wordIndex(state: State): number {
return this.currentDict.wordIndex
},
chapter(state: State): Word[] {
return this.currentDict.chapterWords[this.currentDict.chapterIndex] ?? []
},
//TODO 废弃
word(state: State): Word {
return {trans: [], name: '', usphone: '', ukphone: '',}
},
dictTitle(state: State) {
let title = this.currentDict.name
if ([DictType.publicDict, DictType.customDict].includes(this.current.dictType)) {
title += `${this.currentDict.chapterIndex + 1}`
}
return title
}
},
isArticle(state: State): boolean {
return [
DictType.publicArticle,
DictType.customArticle
].includes(state.current.dictType)
},
currentDict(state: State): Dict {
switch (state.current.dictType) {
case DictType.newDict:
return state.newWordDict
case DictType.skipDict:
return state.skipWordDict
case DictType.wrongDict:
return state.wrongWordDict
case DictType.publicDict:
case DictType.publicArticle:
case DictType.customDict:
case DictType.customArticle:
return this.myDicts[this.current.index]
}
},
currentEditDict(): Dict {
return this.myDicts[this.current.editIndex]
},
wordIndex(state: State): number {
return this.currentDict.wordIndex
},
chapter(state: State): Word[] {
return this.currentDict.chapterWords[this.currentDict.chapterIndex] ?? []
},
//TODO 废弃
word(state: State): Word {
return {trans: [], name: '', usphone: '', ukphone: '',}
},
dictTitle(state: State) {
let title = this.currentDict.name
if ([DictType.publicDict, DictType.customDict].includes(this.current.dictType)) {
title += `${this.currentDict.chapterIndex + 1}`
}
return title
}
},
actions: {
setState(obj: any) {
for (const [key, value] of Object.entries(obj)) {
this[key] = value
}
// console.log('this/', this)
},
async init() {
let configStr = localStorage.getItem(SaveDictKey)
if (configStr) {
let obj: State = JSON.parse(configStr)
// this.setState(obj)
}
actions: {
setState(obj: any) {
for (const [key, value] of Object.entries(obj)) {
this[key] = value
}
// console.log('this/', this)
},
async init() {
let configStr = localStorage.getItem(SaveDictKey)
if (configStr) {
let obj: State = JSON.parse(configStr)
// this.setState(obj)
}
if ([
DictType.newDict,
DictType.wrongDict,
DictType.skipDict,
].includes(this.current.dictType)) {
if ([
DictType.newDict,
DictType.wrongDict,
DictType.skipDict,
].includes(this.current.dictType)) {
} else {
if ([
DictType.publicDict,
DictType.customDict,
].includes(this.current.dictType)) {
if (!this.currentDict.originWords.length) {
let r = await fetch(`${this.currentDict.url}`)
r.json().then(v => {
this.currentDict.originWords = cloneDeep(v)
this.currentDict.words = cloneDeep(v)
this.currentDict.chapterWords = chunk(this.dict.words, this.dict.chapterWordNumber)
this.load = true
})
}
}
} else {
if ([
DictType.publicDict,
DictType.customDict,
].includes(this.current.dictType)) {
if (!this.currentDict.originWords.length) {
let r = await fetch(`${this.currentDict.url}`)
r.json().then(v => {
this.currentDict.originWords = cloneDeep(v)
this.currentDict.words = cloneDeep(v)
this.currentDict.chapterWords = chunk(this.dict.words, this.dict.chapterWordNumber)
this.load = true
})
}
}
if ([
DictType.publicArticle,
DictType.customArticle,
].includes(this.current.dictType)) {
if (!this.currentDict.articles.length) {
let r = await fetch(`${this.currentDict.url}`)
r.json().then(v => {
this.currentDict.articles = cloneDeep(v)
this.load = true
})
}
if ([
DictType.publicArticle,
DictType.customArticle,
].includes(this.current.dictType)) {
if (!this.currentDict.articles.length) {
let r = await fetch(`${this.currentDict.url}`)
r.json().then((v: any[]) => {
this.currentDict.articles = cloneDeep(v.map(v => {
v.id = uuidv4()
return v
}))
this.load = true
})
}
}
}
},
saveStatistics(statistics: Statistics) {
if (statistics.spend > 1000 * 10) {
this.currentDict.statistics.push(statistics)
}
},
async changeDict(dict: Dict, chapterIndex: number = dict.chapterIndex, chapterWordIndex: number = dict.chapterWordNumber) {
this.saveStatistics()
console.log('changeDict', cloneDeep(dict), chapterIndex, chapterWordIndex)
this.current.dictType = dict.type
if ([DictType.newDict,
DictType.skipDict,
DictType.wrongDict].includes(dict.type)) {
this[dict.type].chapterIndex = chapterIndex
this[dict.type].chapterWordIndex = chapterWordIndex
} else {
this.dict = cloneDeep(dict)
if (dict.originWords.length) {
let r = await fetch(`/public/${this.dict.url}`)
let v = await r.json()
this.dict.originWords = cloneDeep(v)
this.dict.words = cloneDeep(v)
this.dict.chapters = chunk(this.dict.words, this.dict.chapterWordNumber)
}
this.dict.chapterIndex = chapterIndex
this.dict.chapterWordIndex = chapterWordIndex
}
emitter.emit(EventKey.resetWord)
}
}
},
saveStatistics(statistics: Statistics) {
if (statistics.spend > 1000 * 10) {
this.currentDict.statistics.push(statistics)
}
},
async changeDict(dict: Dict, chapterIndex: number = dict.chapterIndex, chapterWordIndex: number = dict.chapterWordNumber) {
this.saveStatistics()
console.log('changeDict', cloneDeep(dict), chapterIndex, chapterWordIndex)
this.current.dictType = dict.type
if ([DictType.newDict,
DictType.skipDict,
DictType.wrongDict].includes(dict.type)) {
this[dict.type].chapterIndex = chapterIndex
this[dict.type].chapterWordIndex = chapterWordIndex
} else {
this.dict = cloneDeep(dict)
if (dict.originWords.length) {
let r = await fetch(`/public/${this.dict.url}`)
let v = await r.json()
this.dict.originWords = cloneDeep(v)
this.dict.words = cloneDeep(v)
this.dict.chapters = chunk(this.dict.words, this.dict.chapterWordNumber)
}
this.dict.chapterIndex = chapterIndex
this.dict.chapterWordIndex = chapterWordIndex
}
emitter.emit(EventKey.resetWord)
}
},
})

View File

@@ -1,15 +1,17 @@
import {v4 as uuidv4} from 'uuid';
export type Word = {
"name": string,
"usphone": string,
"ukphone": string,
"trans": string[]
"name": string,
"usphone": string,
"ukphone": string,
"trans": string[]
}
export const DefaultWord: Word = {
name: '',
usphone: '',
ukphone: '',
trans: []
name: '',
usphone: '',
ukphone: '',
trans: []
}
export const SaveDictKey = 'typing-word-dict'
@@ -24,181 +26,183 @@ export type LanguageCategoryType = 'en' | 'ja' | 'de' | 'code'
export type DictionaryResource = {
id: string
name: string
description: string
category: string
tags: string[]
url: string
length: number
language: LanguageType
languageCategory: LanguageCategoryType
//override default pronunciation when not undefined
defaultPronIndex?: number
id: string
name: string
description: string
category: string
tags: string[]
url: string
length: number
language: LanguageType
languageCategory: LanguageCategoryType
//override default pronunciation when not undefined
defaultPronIndex?: number
}
export type Dictionary = {
id: string
name: string
description: string
category: string
tags: string[]
url: string
length: number
language: LanguageType
languageCategory: LanguageCategoryType
// calculated in the store
chapterCount: number
//override default pronunciation when not undefined
defaultPronIndex?: number
id: string
name: string
description: string
category: string
tags: string[]
url: string
length: number
language: LanguageType
languageCategory: LanguageCategoryType
// calculated in the store
chapterCount: number
//override default pronunciation when not undefined
defaultPronIndex?: number
}
export type PronunciationConfig = {
name: string
pron: PronunciationType
name: string
pron: PronunciationType
}
export type LanguagePronunciationMapConfig = {
defaultPronIndex: number
pronunciation: PronunciationConfig[]
defaultPronIndex: number
pronunciation: PronunciationConfig[]
}
export type LanguagePronunciationMap = {
[key in LanguageType]: LanguagePronunciationMapConfig
[key in LanguageType]: LanguagePronunciationMapConfig
}
export type SoundResource = {
key: string
name: string
filename: string
key: string
name: string
filename: string
}
export interface DictJson {
description: string,
category: string,
tags: string[],
url: string,
length: number,
language: string,
languageCategory: string,
description: string,
category: string,
tags: string[],
url: string,
length: number,
language: string,
languageCategory: string,
}
export enum DictType {
newDict = 'newDict',
skipDict = 'skipDict',
wrongDict = 'wrongDict',
publicDict = 'publicDict',
customDict = 'customDict',
publicArticle = 'publicArticle',
customArticle = 'customArticle'
newDict = 'newDict',
skipDict = 'skipDict',
wrongDict = 'wrongDict',
publicDict = 'publicDict',
customDict = 'customDict',
publicArticle = 'publicArticle',
customArticle = 'customArticle'
}
export const DefaultArticleWord: ArticleWord = {
name: '',
usphone: '',
ukphone: '',
trans: [],
nextSpace: true,
isSymbol: false,
symbolPosition: ''
name: '',
usphone: '',
ukphone: '',
trans: [],
nextSpace: true,
isSymbol: false,
symbolPosition: ''
}
export interface ArticleWord extends Word {
nextSpace: boolean,
isSymbol: boolean,
symbolPosition: 'start' | 'end' | '',
nextSpace: boolean,
isSymbol: boolean,
symbolPosition: 'start' | 'end' | '',
}
export interface Sentence {
text: string,
translate: string,
words: ArticleWord[]
text: string,
translate: string,
words: ArticleWord[]
}
export enum TranslateType {
custom = 'custom',
network = 'network',
none = 'none'
custom = 'custom',
network = 'network',
none = 'none'
}
export interface Article {
title: string,
titleTranslate: string,
text: string,
textCustomTranslate: string,
textCustomTranslateIsFormat: boolean,//翻译是否格式化
textNetworkTranslate: string,
newWords: Word[],
textAllWords: string[],
sections: Sentence[][],
useTranslateType: TranslateType
id: string,
title: string,
titleTranslate: string,
text: string,
textCustomTranslate: string,
textCustomTranslateIsFormat: boolean,//翻译是否格式化
textNetworkTranslate: string,
newWords: Word[],
textAllWords: string[],
sections: Sentence[][],
useTranslateType: TranslateType
}
export const DefaultArticle: Article = {
title: '',
titleTranslate: '',
text: '',
textCustomTranslate: '',
textNetworkTranslate: '',
textCustomTranslateIsFormat: false,
newWords: [],
textAllWords: [],
sections: [],
useTranslateType: TranslateType.network
id: uuidv4(),
title: '',
titleTranslate: '',
text: '',
textCustomTranslate: '',
textNetworkTranslate: '',
textCustomTranslateIsFormat: false,
newWords: [],
textAllWords: [],
sections: [],
useTranslateType: TranslateType.network
}
export interface Dict {
name: string,
sort: Sort,
type: DictType,
originWords: Word[],//原始单词
words: Word[],
chapterWordNumber: number,//章节单词数量
chapterWords: Word[][],
articles: Article[],
chapterIndex: number,
chapterWordIndex: number,
statistics: Statistics[],
url: string,
name: string,
sort: Sort,
type: DictType,
originWords: Word[],//原始单词
words: Word[],
chapterWordNumber: number,//章节单词数量
chapterWords: Word[][],
articles: Article[],
chapterIndex: number,
chapterWordIndex: number,
statistics: Statistics[],
url: string,
}
export interface Statistics {
startDate: number,//开始日期
endDate: number//结束日期
spend: number,//花费时间
total: number//单词数量
wrongWordNumber: number//错误数
correctRate: number//正确率
startDate: number,//开始日期
endDate: number//结束日期
spend: number,//花费时间
total: number//单词数量
wrongWordNumber: number//错误数
correctRate: number//正确率
}
export interface DisplayStatistics extends Statistics {
wrongWords: Word[]
wrongWords: Word[]
}
export const DefaultDisplayStatistics: DisplayStatistics = {
startDate: Date.now(),
endDate: -1,
spend: -1,
total: -1,
correctRate: -1,
wrongWordNumber: -1,
wrongWords: [],
startDate: Date.now(),
endDate: -1,
spend: -1,
total: -1,
correctRate: -1,
wrongWordNumber: -1,
wrongWords: [],
}
export enum Sort {
normal = 0,
random = 1,
reverse = 2
normal = 0,
random = 1,
reverse = 2
}
export const ShortKeyMap = {
Show: 'Escape',
Ignore: 'Tab',
Remove: '`',
Collect: 'Enter',
Show: 'Escape',
Ignore: 'Tab',
Remove: '`',
Collect: 'Enter',
}
export enum TranslateEngine {
Baidu = 0,
Baidu = 0,
}