This commit is contained in:
zyronon
2023-11-19 01:25:51 +08:00
parent 0ea816dc73
commit 642e1e3dee
16 changed files with 254 additions and 786 deletions

3
components.d.ts vendored
View File

@@ -9,12 +9,10 @@ declare module 'vue' {
export interface GlobalComponents {
Add: typeof import('./src/components/Toolbar/Add.vue')['default']
AddDict: typeof import('./src/components/Add/AddDict.vue')['default']
AddWordDialog: typeof import('./src/components/Modal/AddWordDialog.vue')['default']
ArticleList: typeof import('./src/components/Article/ArticleList.vue')['default']
Backgorund: typeof import('./src/components/Backgorund.vue')['default']
BaseButton: typeof import('./src/components/BaseButton.vue')['default']
BaseIcon: typeof import('./src/components/BaseIcon.vue')['default']
ChapterDetail: typeof import('./src/components/ChapterDetail.vue')['default']
ChapterList: typeof import('./src/components/list/ChapterList.vue')['default']
ChapterName: typeof import('./src/components/Toolbar/ChapterName.vue')['default']
Close: typeof import('./src/components/icon/Close.vue')['default']
@@ -23,7 +21,6 @@ declare module 'vue' {
DictGroup: typeof import('./src/components/Toolbar/DictGroup.vue')['default']
DictItem: typeof import('./src/components/list/DictItem.vue')['default']
DictList: typeof import('./src/components/list/DictList.vue')['default']
DictModal: typeof import('./src/components/Modal/DictModal.vue')['default']
DictModal2: typeof import('./src/components/Modal/DictModal2.vue')['default']
EditAbleText: typeof import('./src/components/EditAbleText.vue')['default']
EditArticle: typeof import('./src/components/Article/EditArticle.vue')['default']

View File

@@ -10,6 +10,7 @@ import {cloneDeep} from "lodash-es";
import Backgorund from "@/components/Backgorund.vue";
import useTheme from "@/hooks/useTheme.ts";
import * as localforage from "localforage";
import {useStartKeyboardEventListener} from "@/hooks/event.ts";
const store = useBaseStore()
const runtimeStore = useRuntimeStore()
@@ -33,6 +34,7 @@ watch(settingStore.$state, (n) => {
//检测几个特定词典
watch(store.collect.originWords, (n) => {
console.log('watch(store.collect.originWords', n)
store.collect.words = cloneDeep(n)
store.collect.chapterWords = [store.collect.words]
})
@@ -59,7 +61,7 @@ onMounted(() => {
init()
})
// useStartKeyboardEventListener()
useStartKeyboardEventListener()
</script>

View File

@@ -1,40 +0,0 @@
<script setup lang="ts">
import {Icon} from "@iconify/vue";
import {computed, inject} from "vue"
import WordList from "@/components/list/WordList.vue"
import {useBaseStore} from "@/stores/base.ts"
const store = useBaseStore()
const back: () => void = inject('back')
const stepIndex: any = inject('stepIndex')
const tabIndex: any = inject('tabIndex')
const isActive = computed(() => {
return stepIndex.value === 2 && tabIndex.value === 0 && store.sideIsOpen
})
</script>
<template>
<div class="chapter-detail page">
<header>
<Icon icon="octicon:arrow-right-24" @click="back" width="20" color="#929596"/>
<div class="dict-name">16.</div>
</header>
<WordList :isActive="isActive" :list="store.chapter" :activeIndex="store.wordIndex"></WordList>
</div>
</template>
<style scoped lang="scss">
.chapter-wrapper {
header {
display: flex;
align-items: center;
gap: 10rem;
margin-bottom: 10rem;
.dict-name {
font-size: 26rem;
}
}
}
</style>

View File

@@ -36,12 +36,13 @@ let wordList = $ref([])
let step = $ref(1)
let loading = $ref(false)
let show = $ref(false)
watch(() => runtimeStore.showDictModal, (n: boolean) => {
runtimeStore.editDict = cloneDeep(store.currentDict)
})
function close() {
show = false
}
async function selectDict(val: { dict: DictResource, index: number }) {
async function selectDict(val: { dict: DictResource | Dict, index: number }) {
let item = val.dict
console.log('item', item)
step = 1
@@ -49,7 +50,7 @@ async function selectDict(val: { dict: DictResource, index: number }) {
detailListTabIndex = 0
wordFormMode = FormMode.None
loading = true
let find: Dict = store.myDictList.find((v: Dict) => v.name === item.name)
let find: Dict = store.myDictList.find((v: Dict) => v.id === item.id)
if (find) {
runtimeStore.editDict = cloneDeep(find)
} else {
@@ -90,8 +91,6 @@ async function selectDict(val: { dict: DictResource, index: number }) {
}))
}
}
}
loading = false
}
@@ -101,9 +100,6 @@ function changeDict() {
close()
}
function close() {
runtimeStore.showDictModal = false
}
function groupByDictTags(dictList: DictResource[]) {
return dictList.reduce<Record<string, DictResource[]>>((result, dict) => {
@@ -218,26 +214,6 @@ const dictRules = reactive<FormRules>({
watch(() => dictForm.language, () => isAddDict && (dictForm.category = ''))
watch(() => dictForm.category, () => isAddDict && (dictForm.tags = []))
onMounted(() => {
dictionaryResources.map(v => {
if (categoryList[v.language]) {
if (!categoryList[v.language].find(w => w === v.category)) {
categoryList[v.language].push(v.category)
}
} else {
categoryList[v.language] = [v.category]
}
if (tagList[v.category]) {
tagList[v.category] = Array.from(new Set(tagList[v.category].concat(v.tags)))
} else {
tagList[v.category] = v.tags
}
})
// console.log('categoryList', categoryList)
// console.log('tagList', tagList)
})
function editDict() {
// dictForm.id = store.editDict.id
// dictForm.name = store.editDict.name
@@ -292,6 +268,10 @@ async function onSubmit() {
})
}
/**/
/*词典相关*/
/**/
/**/
/* 单词修改相关*/
@@ -440,6 +420,10 @@ function addWord() {
wordForm = cloneDeep(DefaultFormWord)
}
/**/
/* 单词修改相关*/
/**/
watch(() => step, v => {
if (v === 0) {
closeWordForm()
@@ -447,12 +431,58 @@ watch(() => step, v => {
}
})
onMounted(() => {
dictionaryResources.map(v => {
if (categoryList[v.language]) {
if (!categoryList[v.language].find(w => w === v.category)) {
categoryList[v.language].push(v.category)
}
} else {
categoryList[v.language] = [v.category]
}
if (tagList[v.category]) {
tagList[v.category] = Array.from(new Set(tagList[v.category].concat(v.tags)))
} else {
tagList[v.category] = v.tags
}
})
emitter.on(EventKey.openDictModal, (type: 'detail' | 'list' | 'my' | 'collect' | 'simple') => {
if (type === "detail") {
selectDict({dict: store.currentDict, index: 0})
}
if (type === "list") {
currentLanguage = 'en'
step = 0
}
if (type === "my") {
currentLanguage = 'my'
step = 0
}
if (type === "collect") {
selectDict({dict: store.collect, index: 0})
wordFormMode = FormMode.Add
addWord()
}
if (type === "simple") {
selectDict({dict: store.simple, index: 0})
addWord()
}
show = true
})
// console.log('categoryList', categoryList)
// console.log('tagList', tagList)
})
</script>
<template>
<Modal
:header="false"
v-model="runtimeStore.showDictModal"
v-model="show"
:show-close="false">
<div id="DictDialog">
<Slide :slide-count="2" :step="step">

View File

@@ -1,550 +0,0 @@
<script setup lang="ts">
import {dictionaryResources} from '@/assets/dictionary.ts'
import {useBaseStore} from "@/stores/base.ts"
import {watch} from "vue"
import {DefaultDict, Dict, DictResource, DictType, languageCategoryOptions, Sort} from "@/types.ts"
import {chunk, cloneDeep, groupBy, reverse, shuffle} from "lodash-es";
import {$computed, $ref} from "vue/macros";
import Modal from "@/components/Modal/Modal.vue";
import BaseButton from "@/components/BaseButton.vue";
import {Icon} from '@iconify/vue';
import DictGroup from "@/components/Toolbar/DictGroup.vue";
import {v4 as uuidv4} from "uuid";
import {ActivityCalendar} from "vue-activity-calendar";
import "vue-activity-calendar/style.css";
import ChapterList from "@/components/list/ChapterList.vue";
import WordListModal from "@/components/Modal/WordListModal.vue";
import {isArticle} from "@/hooks/article.ts";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {useSettingStore} from "@/stores/setting.ts";
import {emitter, EventKey} from "@/utils/eventBus.ts";
const baseStore = useBaseStore()
const settingStore = useSettingStore()
const runtimeStore = useRuntimeStore()
let currentLanguage = $ref('en')
let currentTranslateLanguage = $ref('common')
let groupByLanguage = groupBy(dictionaryResources, 'language')
let translateLanguageList = $ref([])
let step = $ref(1)
let loading = $ref(false)
watch(() => runtimeStore.showDictModal, (n: boolean) => {
runtimeStore.editDict = cloneDeep(baseStore.currentDict)
})
async function selectDict(item: DictResource) {
console.log('item', item)
step = 1
loading = true
let find: Dict = baseStore.myDictList.find((v: Dict) => v.name === item.name)
if (find) {
runtimeStore.editDict = cloneDeep(find)
if (find.type === DictType.article) {
if (!find.articles.length) {
let r = await fetch(`./dicts/${find.language}/${find.type}/${find.translateLanguage}/${find.url}`)
let v = await r.json()
runtimeStore.editDict.articles = v.map(s => {
s.id = uuidv4()
return s
})
}
}
loading = false
} else {
let data: Dict = {
...cloneDeep(DefaultDict),
...item,
}
runtimeStore.editDict = cloneDeep(data)
let r = await fetch(`./dicts/${data.language}/${data.type}/${data.translateLanguage}/${item.url}`)
r.json().then(v => {
console.log('v', v)
if (data.type === DictType.article) {
runtimeStore.editDict.articles = cloneDeep(v.map(s => {
s.id = uuidv4()
return s
}))
} else {
runtimeStore.editDict.originWords = v
runtimeStore.editDict.words = v
runtimeStore.editDict.chapterWords = chunk(v, runtimeStore.editDict.chapterWordNumber)
console.log(' runtimeStore.editDict', runtimeStore.editDict)
}
loading = false
})
}
}
function changeDict() {
baseStore.changeDict(runtimeStore.editDict)
close()
}
function close() {
runtimeStore.showDictModal = false
}
function groupByDictTags(dictList: DictResource[]) {
return dictList.reduce<Record<string, DictResource[]>>((result, dict) => {
dict.tags.forEach((tag) => {
if (Object.prototype.hasOwnProperty.call(result, tag)) {
result[tag].push(dict)
} else {
result[tag] = [dict]
}
})
return result
}, {})
}
const groupByTranslateLanguage = $computed(() => {
let data: any
if (currentLanguage === 'article') {
let articleList = dictionaryResources.filter(v => v.type === 'article')
data = groupBy(articleList, 'translateLanguage')
} else {
data = groupBy(groupByLanguage[currentLanguage], 'translateLanguage')
}
translateLanguageList = Object.keys(data)
currentTranslateLanguage = translateLanguageList[0]
return data
})
const groupedByCategoryAndTag = $computed(() => {
const currentTranslateLanguageDictList = groupByTranslateLanguage[currentTranslateLanguage]
const groupByCategory = groupBy(currentTranslateLanguageDictList, 'category')
let data = []
for (const [key, value] of Object.entries(groupByCategory)) {
data.push([key, groupByDictTags(value)])
}
// console.log('data', data)
return data
})
function clickEvent(e) {
console.log('e', e)
}
const dictIsArticle = $computed(() => {
return isArticle(runtimeStore.editDict.type)
})
function showAllWordModal() {
emitter.emit(EventKey.openWordListModal, {
title: runtimeStore.editDict.name,
translateLanguage: runtimeStore.editDict.translateLanguage,
list: runtimeStore.editDict.words
})
}
function resetChapterList() {
runtimeStore.editDict.chapterWords = chunk(runtimeStore.editDict.words, runtimeStore.editDict.chapterWordNumber)
}
function changeSort(v) {
if (v === Sort.normal) {
runtimeStore.editDict.words = cloneDeep(runtimeStore.editDict.originWords)
} else if (v === Sort.random) {
runtimeStore.editDict.words = shuffle(cloneDeep(runtimeStore.editDict.originWords))
} else {
runtimeStore.editDict.words = reverse(cloneDeep(runtimeStore.editDict.originWords))
}
resetChapterList()
}
</script>
<template>
<Modal
:header="false"
v-model="runtimeStore.showDictModal"
:show-close="false">
<div class="slide">
<div class="slide-list" :class="`step${step}`">
<div class="dict-page">
<header>
<div class="tabs">
<div class="tab"
:class="currentLanguage === item.id && 'active'"
@click="currentLanguage = item.id"
v-for="item in languageCategoryOptions">
<img :src='item.flag'/>
<span>{{ item.name }}</span>
</div>
</div>
<Icon @click="close"
class="hvr-grow pointer"
width="20" color="#929596"
icon="ion:close-outline"/>
</header>
<div class="page-content">
<div class="dict-list-wrapper">
<div class="translate">
<span>翻译</span>
<el-radio-group v-model="currentTranslateLanguage">
<el-radio-button border v-for="i in translateLanguageList" :label="i">{{ i }}</el-radio-button>
</el-radio-group>
</div>
<DictGroup
v-for="item in groupedByCategoryAndTag"
:select-dict-name="runtimeStore.editDict.resourceId"
@selectDict="selectDict"
@detail="step = 1"
:groupByTag="item[1]"
:category="item[0]"
/>
</div>
</div>
</div>
<div class="dict-detail-page">
<header>
<div class="left" @click.stop="step = 0">
<Icon icon="octicon:arrow-left-24" class="go" width="20"/>
<div class="title">
词典详情
</div>
</div>
<Icon @click="close"
class="hvr-grow pointer"
width="20" color="#929596"
icon="ion:close-outline"/>
</header>
<div class="page-content">
<div class="detail">
<div class="name">{{ runtimeStore.editDict.name }}</div>
<div class="desc">{{ runtimeStore.editDict.description }}</div>
<div class="num"
v-if="dictIsArticle"
>总文章{{ runtimeStore.editDict.articles.length }}
</div>
<div class="num" v-else>
总词汇<span class="count"
@click="showAllWordModal"
>{{ runtimeStore.editDict.originWords.length }}</span>
</div>
<div class="num">开始日期-</div>
<div class="num">花费时间-</div>
<div class="num">累积错误-</div>
<div class="num">进度
<el-progress :percentage="0"
:stroke-width="8"
:show-text="false"/>
</div>
</div>
<div class="setting">
<div class="common-title">学习设置</div>
<div class="row" v-if="!isArticle(runtimeStore.editDict.type)">
<div class="label">每章单词数</div>
<el-slider :min="10"
:step="10"
:max="100"
v-model="runtimeStore.editDict.chapterWordNumber"
@change="resetChapterList"
/>
<div class="option">
<span>{{ runtimeStore.editDict.chapterWordNumber }}</span>
</div>
</div>
<div class="row">
<div class="label">单词顺序</div>
<div class="option">
<el-radio-group v-model="runtimeStore.editDict.sort"
@change="changeSort"
>
<el-radio :label="Sort.normal" size="large">默认</el-radio>
<el-radio :label="Sort.random" size="large">随机</el-radio>
<el-radio :label="Sort.reverse" size="large">反转</el-radio>
</el-radio-group>
</div>
</div>
<div class="row">
<div class="label">学习模式</div>
<div class="option">
<el-radio-group v-model="settingStore.dictation">
<el-radio :label="false" size="large">再认</el-radio>
<el-radio :label="true" size="large">拼写</el-radio>
</el-radio-group>
</div>
</div>
<div class="row">
<div class="label">单词发音</div>
<div class="option">
<el-radio-group v-model="settingStore.wordSoundType">
<el-radio label="us" size="large">美音</el-radio>
<el-radio label="uk" size="large">英音</el-radio>
</el-radio-group>
</div>
</div>
<div class="row">
<div class="label">单词自动发音</div>
<div class="option">
<el-switch v-model="settingStore.wordSound"
inline-prompt
active-text=""
inactive-text=""
/>
</div>
</div>
<div class="row">
<div class="label">是否显示翻译</div>
<div class="option">
<el-switch v-model="settingStore.translate"
inline-prompt
active-text=""
inactive-text=""
/>
</div>
</div>
<div class="row">
<div class="label">忽略大小写</div>
<div class="option">
<el-switch v-model="settingStore.ignoreCase"
inline-prompt
active-text=""
inactive-text=""
/>
</div>
</div>
</div>
<div class="other" v-loading="loading">
<div class="common-title">
<template v-if="dictIsArticle">
文章列表{{ runtimeStore.editDict.articles.length }}
</template>
<template v-else>
章节列表{{
runtimeStore.editDict.chapterWords.length
}}(每章{{ runtimeStore.editDict.chapterWordNumber }})
</template>
</div>
<ChapterList
:is-article="dictIsArticle"
v-model:active-index="runtimeStore.editDict.chapterIndex"
:dict="runtimeStore.editDict"/>
</div>
</div>
<div v-if="false" class="activity">
<ActivityCalendar
:data="[{ date: '2023-05-22', count: 5 }]"
:width="40"
:height="7"
:cellLength="16"
:cellInterval="8"
:fontSize="12"
:showLevelFlag="false"
:showWeekDayFlag="false"
:clickEvent="clickEvent"
/>
</div>
<div class="footer">
<!-- <BaseButton @click="step = 0">导出</BaseButton>-->
<BaseButton @click="close">关闭</BaseButton>
<BaseButton @click="changeDict">切换</BaseButton>
</div>
</div>
</div>
</div>
</Modal>
<WordListModal/>
</template>
<style scoped lang="scss">
@import "@/assets/css/variable.scss";
$modal-mask-bg: rgba(#000, .15);
$radius: 16rem;
$time: 0.3s;
$header-height: 60rem;
.slide {
width: 1000rem;
height: 75vh;
.slide-list {
width: 200%;
height: 100%;
display: flex;
transition: all .5s;
}
.step1 {
transform: translate3d(-50%, 0, 0);
}
}
.dict-page {
width: 50%;
height: 100%;
$header-height: 60rem;
padding: var(--space);
padding-top: 0;
box-sizing: border-box;
header {
display: flex;
justify-content: space-between;
align-items: center;
height: $header-height;
.tabs {
display: flex;
gap: 20rem;
.tab {
color: var(--color-font-1);
cursor: pointer;
padding: 10rem;
padding-bottom: 5rem;
transition: all .5s;
border-bottom: 2px solid transparent;
display: flex;
align-items: center;
gap: 6rem;
&.active {
border-bottom: 2px solid $main;
}
img {
height: 30rem;
}
}
}
}
.page-content {
display: flex;
height: calc(100% - $header-height);
.dict-list-wrapper {
flex: 1;
overflow: auto;
height: 100%;
padding-right: var(--space);
.translate {
display: flex;
align-items: center;
color: var(--color-font-1);
margin-bottom: 30rem;
& > span {
font-size: 22rem;
}
}
}
}
}
.dict-detail-page {
width: 50%;
height: 100%;
$header-height: 60rem;
padding: var(--space);
padding-top: 0;
box-sizing: border-box;
display: flex;
flex-direction: column;
header {
cursor: pointer;
width: 100%;
display: flex;
height: $header-height;
align-items: center;
justify-content: space-between;
color: var(--color-font-3);
.left {
display: flex;
gap: 10rem;
align-items: center;
}
}
.page-content {
flex: 1;
overflow: hidden;
display: flex;
position: relative;
gap: var(--space);
.detail {
overflow: auto;
flex: 3;
display: flex;
flex-direction: column;
gap: 10rem;
min-height: 100rem;
position: relative;
border-radius: 10rem;
background: var(--color-second-bg);
color: var(--color-font-1);
font-size: 14rem;
.name {
font-size: 28rem;
margin-bottom: 10rem;
}
.desc {
font-size: 18rem;
margin-bottom: 30rem;
}
.count {
cursor: pointer;
border-bottom: 2px solid var(--color-item-active);
}
}
.setting {
overflow: auto;
flex: 5;
background: white;
border-radius: 10rem;
background: var(--color-second-bg);
color: var(--color-font-1);
.row {
display: flex;
align-items: center;
justify-content: space-between;
height: 40rem;
word-break: keep-all;
gap: 10rem;
.el-radio {
margin-right: 10rem;
}
}
}
.other {
flex: 5;
border-radius: 10rem;
background: var(--color-second-bg);
color: var(--color-font-1);
display: flex;
flex-direction: column;
}
}
.activity {
display: flex;
justify-content: center;
}
.footer {
box-sizing: content-box;
display: flex;
align-items: flex-end;
justify-content: flex-end;
gap: var(--space);
}
}
</style>

View File

@@ -133,6 +133,13 @@ onUnmounted(() => {
box-sizing: border-box;
position: absolute;
bottom: 0;
}
:deep(.el-progress-bar__inner) {
background: var(--color-scrollbar);
}
}
</style>

View File

@@ -3,7 +3,7 @@ import {useBaseStore} from "@/stores/base.ts"
import WordList from "@/components/list/WordList.vue"
import {$ref} from "vue/macros"
import {computed, provide, watch} from "vue"
import {computed, onMounted, provide, watch} from "vue"
import {Dict, DictType, ShortcutKey} from "@/types.ts"
import PopConfirm from "@/components/PopConfirm.vue"
import BaseButton from "@/components/BaseButton.vue";
@@ -34,10 +34,13 @@ let practiceType = $ref(DictType.word)
function changeIndex(i: number, dict: Dict) {
store.changeDict(dict, dict.chapterIndex, i, practiceType)
setTimeout(() => {
}
onMounted(() => {
emitter.on(EventKey.changeDict, () => {
tabIndex = 0
})
}
})
const {
delWrongWord,
@@ -79,17 +82,17 @@ const {
<el-radio-button border :label="DictType.word">单词</el-radio-button>
<el-radio-button border :label="DictType.article">文章</el-radio-button>
</el-radio-group>
<Tooltip title="添加">
<IconWrapper>
<Icon icon="fluent:add-12-regular" @click="emitter.emit(EventKey.editDict,store.collect)"/>
</IconWrapper>
</Tooltip>
<div class="dict-name" v-if="practiceType === DictType.word && store.collect.words.length">
{{ store.collect.words.length }}个单词
</div>
<div class="dict-name" v-if="practiceType === DictType.article && store.collect.articles.length">
{{ store.collect.articles.length }}篇文章
</div>
<Tooltip title="添加">
<IconWrapper>
<Icon icon="fluent:add-12-regular" @click="emitter.emit(EventKey.openDictModal,'collect')"/>
</IconWrapper>
</Tooltip>
</div>
<template v-if="store.currentDict.type !== DictType.collect &&
(
@@ -130,9 +133,16 @@ const {
</div>
</div>
<div class="slide-item">
<div class="panel-page-item" v-if="store.simple.words.length">
<div class="panel-page-item">
<div class="list-header">
<div class="dict-name">总词数:{{ store.simple.words.length }}</div>
<div class="left">
<div class="dict-name">总词数:{{ store.simple.words.length }}</div>
<Tooltip title="添加">
<IconWrapper>
<Icon icon="fluent:add-12-regular" @click="emitter.emit(EventKey.openDictModal,'simple')"/>
</IconWrapper>
</Tooltip>
</div>
<template v-if="store.currentDict.type !== DictType.simple && store.simple.words.length">
<PopConfirm
:title="`确认切换?`"
@@ -143,6 +153,7 @@ const {
</template>
</div>
<CommonWordList
v-if="store.simple.words.length"
class="word-list"
:list="store.simple.words">
<template v-slot="{word,index}">
@@ -153,8 +164,8 @@ const {
icon="solar:trash-bin-minimalistic-linear"/>
</template>
</CommonWordList>
<Empty v-else/>
</div>
<Empty v-else/>
</div>
<div class="slide-item">
<div class="panel-page-item" v-if="store.wrong.words.length">

View File

@@ -99,7 +99,7 @@ function openSetting() {
}
function openDictDetail() {
runtimeStore.showDictModal = true
emitter.emit(EventKey.openDictModal, 'detail')
}
function toggleConciseMode() {
@@ -164,7 +164,7 @@ onUnmounted(() => {
<PracticeWord ref="practiceRef" v-else/>
<Footer/>
</div>
<!-- <AddWordDialog></AddWordDialog>-->
<!-- <AddWordDialog></AddWordDialog>-->
<DictModal/>
<SettingModal v-if="runtimeStore.showSettingModal" @close="runtimeStore.showSettingModal = false"/>
<Statistics/>

View File

@@ -247,7 +247,7 @@ function changePracticeArticle(val: Article) {
<div class="left">
<Tooltip title="切换词典">
<IconWrapper>
<Icon @click="runtimeStore.showDictModal = true" icon="basil:exchange-outline"/>
<Icon @click="emitter.emit(EventKey.openDictModal,'list')" icon="basil:exchange-outline"/>
</IconWrapper>
</Tooltip>
<div class="title">

View File

@@ -280,7 +280,7 @@ onUnmounted(() => {
<div class="left">
<Tooltip title="切换词典">
<IconWrapper>
<Icon @click="runtimeStore.showDictModal = true" icon="basil:exchange-outline"/>
<Icon @click="emitter.emit(EventKey.openDictModal,'list')" icon="basil:exchange-outline"/>
</IconWrapper>
</Tooltip>
<div class="title">
@@ -320,13 +320,13 @@ onUnmounted(() => {
<BaseIcon
v-if="!isWordSimple(word)"
class-name="easy"
@click="toggleWordCollect(word)"
@click="toggleWordSimple(word)"
title="标记为简单词"
icon="material-symbols:check-circle-outline-rounded"/>
<BaseIcon
v-else
class-name="fill"
@click="toggleWordCollect(word)"
@click="toggleWordSimple(word)"
title="取消标记简单词"
icon="material-symbols:check-circle-rounded"/>
</template>

View File

@@ -17,6 +17,7 @@ import {useRuntimeStore} from "@/stores/runtime.ts";
import {$ref} from "vue/macros";
import {ShortcutKey} from "@/types.ts";
import ChapterName from "@/components/Toolbar/ChapterName.vue";
import {emitter, EventKey} from "@/utils/eventBus.ts";
const {toggleTheme} = useTheme()
const store = useBaseStore()
@@ -64,7 +65,7 @@ watch(() => store.load, n => {
<div class="dict-name">
<Tooltip
:title="`词典详情(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.OpenDictDetail]})`">
<div class="info hvr-grow" @click="runtimeStore.showDictModal = true">
<div class="info hvr-grow" @click="emitter.emit(EventKey.openDictModal,'detail')">
{{ store.currentDict.name }} {{ practiceStore.repeatNumber ? ' 复习错词' : '' }}
</div>
</Tooltip>
@@ -102,7 +103,16 @@ watch(() => store.load, n => {
<RepeatSetting/>
<!-- <Add/>-->
<!-- <Add/>-->
<Tooltip title="添加">
<IconWrapper>
<Icon icon="ic:outline-cloud-upload"
@click="emitter.emit(EventKey.openDictModal,'my')"
/>
</IconWrapper>
</Tooltip>
<Tooltip title="反馈">
<IconWrapper>

View File

@@ -23,7 +23,7 @@ const emit = defineEmits<{
<template v-if="dict.name">
<div class="name">{{ dict.name }}</div>
<div class="desc">{{ dict.description }}</div>
<div class="num">{{ dict.length }}</div>
<div class="num">{{ dict.length ?? dict.originWords.length }}</div>
</template>
<div v-else class="add" @click.stop="emit('add')">
<Icon icon="fluent:add-20-filled" width="38" color="#929596"/>

View File

@@ -1,64 +1,117 @@
import {Word} from "@/types.ts";
import {Dict, DictType, Word} from "@/types.ts";
import {useBaseStore} from "@/stores/base.ts";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {chunk, cloneDeep} from "lodash-es";
import {v4 as uuidv4} from "uuid";
export function useWordOptions() {
const store = useBaseStore()
const store = useBaseStore()
function isWordCollect(val: Word) {
return !!store.collect.originWords.find(v => v.name.toLowerCase() === val.name.toLowerCase())
}
function toggleWordCollect(val: Word) {
let rIndex = store.collect.originWords.findIndex(v => v.name.toLowerCase() === val.name.toLowerCase())
if (rIndex > -1) {
store.collect.originWords.splice(rIndex, 1)
} else {
let rIndex = store.simple.originWords.findIndex(v => v.name.toLowerCase() === val.name.toLowerCase())
if (rIndex > -1) {
store.simple.originWords.splice(rIndex, 1)
}
store.collect.originWords.push(val)
function isWordCollect(val: Word) {
return !!store.collect.originWords.find(v => v.name.toLowerCase() === val.name.toLowerCase())
}
}
function isWordSimple(val: Word) {
return !!store.simple.originWords.find(v => v.name.toLowerCase() === val.name.toLowerCase())
}
function toggleWordSimple(val: Word) {
let rIndex = store.simple.originWords.findIndex(v => v.name.toLowerCase() === val.name.toLowerCase())
if (rIndex > -1) {
store.simple.originWords.splice(rIndex, 1)
} else {
let rIndex = store.collect.originWords.findIndex(v => v.name.toLowerCase() === val.name.toLowerCase())
if (rIndex > -1) {
store.collect.originWords.splice(rIndex, 1)
}
store.simple.originWords.push(val)
function toggleWordCollect(val: Word) {
let rIndex = store.collect.originWords.findIndex(v => v.name.toLowerCase() === val.name.toLowerCase())
if (rIndex > -1) {
store.collect.originWords.splice(rIndex, 1)
} else {
let rIndex = store.simple.originWords.findIndex(v => v.name.toLowerCase() === val.name.toLowerCase())
if (rIndex > -1) {
store.simple.originWords.splice(rIndex, 1)
}
store.collect.originWords.push(val)
}
}
}
function delWrongWord(val: Word) {
let rIndex = store.wrong.originWords.findIndex(v => v.name.toLowerCase() === val.name.toLowerCase())
if (rIndex > -1) {
store.wrong.originWords.splice(rIndex, 1)
function isWordSimple(val: Word) {
return !!store.simple.originWords.find(v => v.name.toLowerCase() === val.name.toLowerCase())
}
}
function delSimpleWord(val: Word) {
let rIndex = store.simple.originWords.findIndex(v => v.name.toLowerCase() === val.name.toLowerCase())
if (rIndex > -1) {
store.simple.originWords.splice(rIndex, 1)
function toggleWordSimple(val: Word) {
let rIndex = store.simple.originWords.findIndex(v => v.name.toLowerCase() === val.name.toLowerCase())
if (rIndex > -1) {
store.simple.originWords.splice(rIndex, 1)
} else {
let rIndex = store.collect.originWords.findIndex(v => v.name.toLowerCase() === val.name.toLowerCase())
if (rIndex > -1) {
store.collect.originWords.splice(rIndex, 1)
}
store.simple.originWords.push(val)
}
}
}
return {
isWordCollect,
toggleWordCollect,
isWordSimple,
toggleWordSimple,
delWrongWord,
delSimpleWord
}
function delWrongWord(val: Word) {
let rIndex = store.wrong.originWords.findIndex(v => v.name.toLowerCase() === val.name.toLowerCase())
if (rIndex > -1) {
store.wrong.originWords.splice(rIndex, 1)
}
}
function delSimpleWord(val: Word) {
let rIndex = store.simple.originWords.findIndex(v => v.name.toLowerCase() === val.name.toLowerCase())
if (rIndex > -1) {
store.simple.originWords.splice(rIndex, 1)
}
}
return {
isWordCollect,
toggleWordCollect,
isWordSimple,
toggleWordSimple,
delWrongWord,
delSimpleWord
}
}
export async function checkDictHasTranslate(dict: Dict) {
let dictResourceUrl = `./dicts/${dict.language}/${dict.type}/${dict.translateLanguage}/${dict.url}`;
if ([
DictType.word,
DictType.customWord,
].includes(dict.type)) {
if (!dict.originWords.length) {
let r = await fetch(dictResourceUrl)
// let r = await fetch(`.${dict.url}`)
let v = await r.json()
if (dict.translateLanguage === 'common') {
const runtimeStore = useRuntimeStore()
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()
runtimeStore.translateWordList = list
dict.originWords = cloneDeep(v)
dict.words = cloneDeep(v)
dict.chapterWords = chunk(dict.words, dict.chapterWordNumber)
dict.chapterWords[dict.chapterIndex].map((w: Word) => {
let res = list.find(a => a.name === w.name)
if (res) w = Object.assign(w, res)
})
} else {
dict.originWords = cloneDeep(v)
dict.words = cloneDeep(v)
dict.chapterWords = chunk(dict.words, dict.chapterWordNumber)
}
}
}
if ([
DictType.article,
DictType.customArticle,
].includes(dict.type)) {
if (!dict.articles.length) {
let r = await fetch(dictResourceUrl)
let s: any[] = await r.json()
dict.articles = cloneDeep(s.map(v => {
v.id = uuidv4()
return v
}))
}
}
}

View File

@@ -1,17 +1,16 @@
import {defineStore} from 'pinia'
import {DefaultDict, DefaultWord, Dict, DictType, DisplayStatistics, SaveDict, Word} from "../types.ts"
import {chunk, cloneDeep} from "lodash-es";
import {DefaultDict, Dict, DictType, DisplayStatistics, SaveDict, Word} from "../types.ts"
import {chunk, cloneDeep, merge} from "lodash-es";
import {emitter, EventKey} from "@/utils/eventBus.ts"
import {v4 as uuidv4} from 'uuid';
import {useRuntimeStore} from "@/stores/runtime.ts";
import * as localforage from "localforage";
import {sizeofByte} from "@/utils";
import {checkDictHasTranslate} from "@/hooks/dict.ts";
export interface BaseState {
myDictList: Dict[],
current: {
index: number,
editIndex: number,
practiceType: DictType,//练习类型目前仅词典为collect时判断是练单词还是文章使用
},
simpleWords: string[],
@@ -116,7 +115,6 @@ export const useBaseStore = defineStore('base', {
],
current: {
index: 3,
editIndex: 0,
// dictType: DictType.article,
// index: 0,
practiceType: DictType.word,
@@ -134,7 +132,7 @@ export const useBaseStore = defineStore('base', {
},
getters: {
collect() {
return this.myDictList[0]
return this.myDictList[0] ?? {}
},
simple(): Dict {
return this.myDictList[1]
@@ -158,21 +156,9 @@ export const useBaseStore = defineStore('base', {
DictType.customArticle
].includes(this.currentDict.type)
},
editDict(state: BaseState) {
if (state.current.editIndex === -1) {
return cloneDeep(DefaultDict)
}
return state.myDictList.filter(v => [DictType.customWord, DictType.customArticle].includes(v.type))[state.current.editIndex - 3]
},
currentDict(): Dict {
return this.myDictList[this.current.index]
},
currentEditDict(): Dict {
return this.myDictList[this.current.editIndex]
},
wordIndex(state: BaseState): number {
return this.currentDict.wordIndex
},
chapter(state: BaseState): Word[] {
return this.currentDict.chapterWords[this.currentDict.chapterIndex] ?? []
},
@@ -184,9 +170,10 @@ export const useBaseStore = defineStore('base', {
let title = ''
switch (this.currentDict.type) {
case DictType.collect:
if (state.current.practiceType === DictType.word) {
if (state.current.practiceType === DictType.article || state.current.practiceType === DictType.customArticle) {
return `${this.currentDict.chapterIndex + 1}`
}
return ''
case DictType.word:
case DictType.customWord:
return `${this.currentDict.chapterIndex + 1}`
@@ -196,17 +183,16 @@ export const useBaseStore = defineStore('base', {
},
actions: {
setState(obj: any) {
for (const [key, value] of Object.entries(obj)) {
this[key] = value
}
// console.log('this/', this)
//这样不会丢失watch的值的引用
merge(this, obj)
},
async init() {
return new Promise(async resolve => {
try {
let configStr: string = await localforage.getItem(SaveDict.key)
// console.log('s', configStr)
console.log(configStr)
console.log('s', new Blob([configStr]).size)
configStr = ''
if (configStr) {
let data = JSON.parse(configStr)
let state: BaseState = data.val
@@ -219,7 +205,7 @@ export const useBaseStore = defineStore('base', {
}
}
} catch (e) {
console.error('读取本地dict数据失败',e)
console.error('读取本地dict数据失败', e)
}
if (this.current.index < 3) {
@@ -239,21 +225,13 @@ export const useBaseStore = defineStore('base', {
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()
runtimeStore.translateWordList = list
this.currentDict.originWords = cloneDeep(v)
this.currentDict.words = cloneDeep(v)
this.currentDict.chapterWords = chunk(this.currentDict.words, this.currentDict.chapterWordNumber)
this.currentDict.chapterWords[this.currentDict.chapterIndex].map((w: Word) => {
let res = list.find(a => a.name === w.name)
if (res) w = Object.assign(w, res)
})
} else {
this.currentDict.originWords = cloneDeep(v)
this.currentDict.words = cloneDeep(v)
this.currentDict.chapterWords = chunk(this.currentDict.words, this.currentDict.chapterWordNumber)
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)
}
}
@@ -280,38 +258,45 @@ export const useBaseStore = defineStore('base', {
this.currentDict.statistics.push(statistics)
}
},
async changeDict(dict: Dict, chapterIndex: number = dict.chapterIndex, chapterWordIndex: number = dict.chapterWordNumber, practiceType: DictType) {
async changeDict(dict: Dict, chapterIndex: number = dict.chapterIndex, wordIndex: number = dict.wordIndex, practiceType: DictType) {
//TODO 保存统计
// this.saveStatistics()
console.log('changeDict', cloneDeep(dict), chapterIndex, chapterWordIndex)
console.log('changeDict', cloneDeep(dict), chapterIndex, wordIndex)
this.currentDict.type = dict.type
this.current.practiceType = practiceType
if ([DictType.collect,
DictType.simple,
DictType.wrong].includes(dict.type)) {
this[dict.type].chapterIndex = 0
this[dict.type].chapterWordIndex = chapterWordIndex
this[dict.type].chapterWords = [this[dict.type].words]
dict.chapterIndex = 0
dict.wordIndex = wordIndex
dict.chapterWordNumber = dict.words.length
dict.chapterWords = [dict.words]
} else {
if (dict.type === DictType.article || dict.type === DictType.customArticle) {
if (chapterIndex > dict.articles.length) {
dict.chapterIndex = 0
dict.wordIndex = 0
}
} else {
if (chapterIndex > dict.chapterWords.length) {
dict.chapterIndex = 0
dict.wordIndex = 0
}
}
let rIndex = this.myDictList.findIndex((v: Dict) => v.name === dict.name)
if (rIndex > -1) {
this.myDictList[rIndex] = dict
this.current.index = rIndex
} else {
this.myDictList.push(cloneDeep(dict))
this.current.index = this.myDictList.length - 1
}
}
// 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.resetWord)
emitter.emit(EventKey.changeDict)
}
},
})

View File

@@ -3,8 +3,10 @@ import mitt from 'mitt'
export const emitter = mitt()
export const EventKey = {
resetWord: 'resetWord',
changeDict: 'changeDict',
openStatModal: 'openStatModal',
openWordListModal: 'openWordListModal',
openDictModal: 'openDictModal',
openArticleListModal: 'openArticleListModal',
closeOther: 'closeOther',
keydown: 'keydown',

View File

@@ -1,42 +1,3 @@
export function getRandom(a: number, b: number): number {
return Math.random() * (b - a) + a;
}
export function sizeofByte(str, charset = 'utf-16') {
let total = 0
let charCode
charset = charset.toLowerCase()
if (charset === 'utf-8' || charset === 'utf8') {
for (let i = 0, len = str.length; i < len; i++) {
charCode = str.codePointAt(i)
if (charCode <= 0x007f) {
total += 1
} else if (charCode <= 0x07ff) {
total += 2
} else if (charCode <= 0xffff) {
total += 3
} else {
total += 4
i++
}
}
} else if (charset === 'utf-16' || charset === 'utf16') {
for (let i = 0, len = str.length; i < len; i++) {
charCode = str.codePointAt(i)
if (charCode <= 0xffff) {
total += 2
} else {
total += 4
i++
}
}
} else {
total = str.replace(/[^\x00-\xff]/g, 'aa').length
}
return total
}