feat:重构词典

This commit is contained in:
zyronon
2025-08-05 02:59:32 +08:00
parent 1be66fb5cf
commit d3278e581f
27 changed files with 679 additions and 20911 deletions

View File

@@ -1,4 +1,4 @@
import {Article, ArticleWord, DictType, getDefaultArticleWord, Sentence} from "@/types.ts";
import {Article, ArticleWord, getDefaultArticleWord, Sentence} from "@/types.ts";
import {cloneDeep} from "@/utils";
import nlp from "compromise/one";
import {usePlayWordAudio} from "@/hooks/sound.ts";
@@ -526,12 +526,6 @@ export function splitCNArticle2(text: string): string {
return s
}
export function isArticle(type: DictType): boolean {
return [
DictType.article,
].includes(type)
}
export function getTranslateText(article: Article) {
return article.textTranslate
.split('\n\n').filter(v => v)

View File

@@ -1,5 +1,6 @@
import {Article, Word} from "@/types.ts";
import {Article, getDefaultArticle, Word} from "@/types.ts";
import {useBaseStore} from "@/stores/base.ts";
import {nanoid} from "nanoid";
export function useWordOptions() {
@@ -63,11 +64,12 @@ export function useArticleOptions() {
const store = useBaseStore()
function isArticleCollect(val: Article) {
return !!store.collectArticle.articles.find(v => v.title.toLowerCase() === val.title.toLowerCase())
return !!store.collectArticle.articles.find(v => v.id === val.id)
}
//todo 这里先收藏,再修改。收藏里面的未同步。单词也是一样的
function toggleArticleCollect(val: Article) {
let rIndex = store.collectArticle.articles.findIndex(v => v.title.toLowerCase() === val.title.toLowerCase())
let rIndex = store.collectArticle.articles.findIndex(v => v.id === val.id)
if (rIndex > -1) {
store.collectArticle.articles.splice(rIndex, 1)
} else {

View File

@@ -3,84 +3,59 @@ import {useBaseStore} from "@/stores/base.ts";
import {Icon} from '@iconify/vue'
import "vue-activity-calendar/style.css";
import {useRouter} from "vue-router";
import {enArticle} from "@/assets/dictionary.ts";
import BasePage from "@/pages/pc/components/BasePage.vue";
import {useNav} from "@/utils";
import {Dict, DictResource, getDefaultDict} from "@/types.ts";
import {cloneDeep} from "@/utils";
import {_getDictDataByUrl, useNav} from "@/utils";
import {DictResource, DictType, getDefaultDict} from "@/types.ts";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {getArticleBookDataByUrl} from "@/utils/article.ts";
import BaseIcon from "@/components/BaseIcon.vue";
import Dialog from "@/pages/pc/components/dialog/Dialog.vue";
import Input from "@/pages/pc/components/Input.vue";
import {computed} from "vue";
import Book from "@/pages/pc/components/Book.vue";
import {ElMessage, ElProgress} from 'element-plus';
import BaseButton from "@/components/BaseButton.vue";
import PopConfirm from "@/pages/pc/components/PopConfirm.vue";
import {onMounted, watch} from "vue";
const {nav} = useNav()
const base = useBaseStore()
const router = useRouter()
const store = useBaseStore()
const router = useRouter()
const runtimeStore = useRuntimeStore()
let showAddChooseDialog = $ref(false)
let showSearchDialog = $ref(false)
let searchKey = $ref('')
onMounted(init)
watch(() => store.load, init)
async function getBookDetail(val: DictResource) {
let r = await getArticleBookDataByUrl(val)
runtimeStore.editDict = cloneDeep(r)
nav('book-detail')
}
async function getBookDetail2(val: Dict) {
if (!val.name) {
showSearchDialog = true
return
async function init() {
if (store.article.studyIndex >= 1) {
if (!store.sbook.custom && !store.sbook.articles.length) {
store.article.bookList[store.article.studyIndex] = await _getDictDataByUrl(store.sbook, DictType.article)
}
}
runtimeStore.editDict = cloneDeep(val)
nav('book-detail')
}
const searchList = computed(() => {
if (searchKey) {
return enArticle.filter(v => v.name.toLocaleLowerCase().includes(searchKey.toLocaleLowerCase()))
}
return []
})
function addBook() {
showAddChooseDialog = false
runtimeStore.editDict = getDefaultDict()
nav('book-detail', {isAdd: true})
}
function startStudy() {
if (!base.currentBook.name) {
showSearchDialog = true
if (base.sbook.id) {
if (!base.sbook.articles.length) {
return ElMessage.warning('没有文章可学习!')
}
nav('/study-article')
} else {
ElMessage.warning('请先选择一本书籍')
return
}
router.push('/study-article')
}
let isMultiple = $ref(false)
let selectIds = $ref([])
function handleBatchDel() {
selectIds.forEach(id => {
let r = store.word.bookList.findIndex(v => v.id === id)
let r = base.article.bookList.findIndex(v => v.id === id)
if (r !== -1) {
if (store.word.studyIndex === r) {
store.word.studyIndex = -1
if (base.article.studyIndex === r) {
base.article.studyIndex = -1
}
if (store.word.studyIndex > r) {
store.word.studyIndex--
if (base.article.studyIndex > r) {
base.article.studyIndex--
}
store.word.bookList.splice(r, 1)
base.article.bookList.splice(r, 1)
}
})
selectIds = []
@@ -96,10 +71,11 @@ function toggleSelect(item) {
}
}
async function goDictDetail(val: DictResource) {
async function goBookDetail(val: DictResource) {
runtimeStore.editDict = getDefaultDict(val)
nav('book-detail', {})
nav('book-detail')
}
</script>
<template>
@@ -108,10 +84,10 @@ async function goDictDetail(val: DictResource) {
<div class="flex justify-between items-center">
<div class="bg-third p-3 gap-4 rounded-md cursor-pointer flex items-center">
<span class="text-lg font-bold"
@click="getBookDetail2(base.currentBook)">{{
@click="goBookDetail(base.currentBook)">{{
base.currentBook.name || '请选择书籍开始学习'
}}</span>
<BaseIcon @click="showSearchDialog = true"
<BaseIcon @click="router.push('/book-list')"
:icon="base.currentBook.name ? 'gg:arrows-exchange':'fluent:add-20-filled'"/>
</div>
<BaseButton
@@ -137,7 +113,7 @@ async function goDictDetail(val: DictResource) {
<BaseIcon class="del" title="删除" icon="solar:trash-bin-minimalistic-linear"/>
</PopConfirm>
<div class="color-blue cursor-pointer" v-if="store.article.bookList.length > 1"
<div class="color-blue cursor-pointer" v-if="base.article.bookList.length > 1"
@click="isMultiple = !isMultiple; selectIds = []">{{ isMultiple ? '取消' : '管理书籍' }}
</div>
<div class="color-blue cursor-pointer" @click="nav('dict-detail', { isAdd: true })">创建个人书籍</div>
@@ -147,9 +123,9 @@ async function goDictDetail(val: DictResource) {
<Book :is-add="false" quantifier="篇" :item="item" :checked="selectIds.includes(item.id)"
@check="() => toggleSelect(item)"
:show-checkbox="isMultiple && j >= 1"
v-for="(item, j) in store.article.bookList"
@click="goDictDetail(item)"/>
<Book :is-add="true" @click="router.push('/dict-list')"/>
v-for="(item, j) in base.article.bookList"
@click="goBookDetail(item)"/>
<Book :is-add="true" @click="router.push('/book-list')"/>
</div>
</div>
</BasePage>

View File

@@ -15,6 +15,7 @@ import MiniDialog from "@/pages/pc/components/dialog/MiniDialog.vue";
import EditArticle2 from "@/pages/pc/article/components/EditArticle2.vue";
import BaseIcon from "@/components/BaseIcon.vue";
import {_nextTick} from "@/utils";
import {ElMessage} from "element-plus";
const emit = defineEmits<{
importData: [val: Event]

View File

@@ -5,12 +5,15 @@ import BackIcon from "@/pages/pc/components/BackIcon.vue";
import Empty from "@/components/Empty.vue";
import ArticleList from "@/pages/pc/components/list/ArticleList.vue";
import {useBaseStore} from "@/stores/base.ts";
import {Article, getDefaultArticle} from "@/types.ts";
import {Article, DictId, DictType, getDefaultArticle, getDefaultDict} from "@/types.ts";
import {useRuntimeStore} from "@/stores/runtime.ts";
import BaseButton from "@/components/BaseButton.vue";
import {useRoute, useRouter} from "vue-router";
import EditBook from "@/pages/pc/article/components/EditBook.vue";
import {computed, onMounted} from "vue";
import {_getDictDataByUrl} from "@/utils";
import BaseIcon from "@/components/BaseIcon.vue";
import {useArticleOptions} from "@/hooks/dict.ts";
const runtimeStore = useRuntimeStore()
const base = useBaseStore()
@@ -19,29 +22,21 @@ const route = useRoute()
let isEdit = $ref(false)
let isAdd = $ref(false)
let loading = $ref(false)
let studyLoading = $ref(false)
let article: Article = $ref(getDefaultArticle())
let chapterIndex = $ref(-1)
let selectArticle: Article = $ref(getDefaultArticle())
function handleCheckedChange(val) {
let rIndex = runtimeStore.editDict.articles.findIndex(v => v.id === val.item.id)
if (rIndex > -1) {
chapterIndex = rIndex
article = val.item
}
selectArticle = val.item
}
const activeId = $computed(() => {
return runtimeStore.editDict.articles?.[chapterIndex]?.id ?? ''
})
function addMyStudyList() {
let rIndex = base.article.bookList.findIndex(v => v.name === runtimeStore.editDict.name)
if (rIndex > -1) {
base.article.studyIndex = rIndex
} else {
base.article.bookList.push(runtimeStore.editDict)
base.article.studyIndex = base.article.bookList.length - 1
async function addMyStudyList() {
studyLoading = true
base.changeBook(runtimeStore.editDict)
studyLoading = false
if (route.query?.from) {
router.back()
}
router.back()
}
@@ -50,20 +45,42 @@ const showBookDetail = computed(() => {
return !(isAdd || isEdit);
})
onMounted(() => {
async function init() {
if (route.query?.isAdd) {
isAdd = true
}else {
runtimeStore.editDict = getDefaultDict()
} else {
if (!runtimeStore.editDict.id) {
router.push("/article")
await router.push("/article")
} else {
if (!runtimeStore.editDict.articles.length
&& !runtimeStore.editDict.custom
&& ![DictId.articleCollect].includes(runtimeStore.editDict.id)
) {
loading = true
let r = await _getDictDataByUrl(runtimeStore.editDict, DictType.article)
loading = false
runtimeStore.editDict = r
}
if (runtimeStore.editDict.articles.length) {
selectArticle = runtimeStore.editDict.articles[0]
}
}
}
})
}
onMounted(init)
function formClose() {
if (isEdit) isEdit = false
else router.back()
}
const {
isArticleCollect,
toggleArticleCollect
} = useArticleOptions()
</script>
<template>
@@ -75,7 +92,7 @@ function formClose() {
<div class="flex">
<BaseButton type="info" @click="isEdit = true">编辑</BaseButton>
<BaseButton type="info" @click="router.push('batch-edit-article')">文章管理</BaseButton>
<BaseButton @click="addMyStudyList">学习</BaseButton>
<BaseButton :loading="studyLoading" @click="addMyStudyList">学习</BaseButton>
</div>
</div>
<div class="text-lg ">介绍{{ runtimeStore.editDict.description }}</div>
@@ -84,26 +101,41 @@ function formClose() {
<div class="flex flex-1 overflow-hidden">
<div class="left flex-[2] scroll p-0">
<ArticleList
v-if="runtimeStore.editDict.articles.length"
v-if="runtimeStore.editDict.length"
@title="handleCheckedChange"
@click="handleCheckedChange"
:list="runtimeStore.editDict.articles"
:active-id="activeId">
:active-id="selectArticle.id">
<template v-slot:suffix="{item,index}">
<BaseIcon
v-if="!isArticleCollect(item)"
class="collect"
@click="toggleArticleCollect(item)"
title="收藏" icon="ph:star"/>
<BaseIcon
v-else
class="fill"
@click="toggleArticleCollect(item)"
title="取消收藏" icon="ph:star-fill"/>
</template>
</ArticleList>
<Empty v-else/>
</div>
<div class="right flex-[4] shrink-0 pl-4 overflow-auto">
<div v-if="chapterIndex>-1">
<div v-if="selectArticle.id">
<div class="en-article-family title text-xl">
<div class="text-center text-2xl">{{ article.title }}</div>
<div class="text-2xl" v-if="article.text">
<div class="my-5" v-for="t in article.text.split('\n\n')">{{ t }}</div>
<div class="text-center text-2xl">
<audio :src="selectArticle.audioSrc" controls></audio>
</div>
<div class="text-center text-2xl">{{ selectArticle.title }}</div>
<div class="text-2xl" v-if="selectArticle.text">
<div class="my-5" v-for="t in selectArticle.text.split('\n\n')">{{ t }}</div>
</div>
</div>
<div class="mt-2">
<div class="text-center text-2xl">{{ article.titleTranslate }}</div>
<div class="text-xl" v-if="article.textTranslate">
<div class="my-5" v-for="t in article.textTranslate.split('\n\n')">{{ t }}</div>
<div class="text-center text-2xl">{{ selectArticle.titleTranslate }}</div>
<div class="text-xl" v-if="selectArticle.textTranslate">
<div class="my-5" v-for="t in selectArticle.textTranslate.split('\n\n')">{{ t }}</div>
</div>
<Empty v-else/>
</div>
@@ -116,9 +148,7 @@ function formClose() {
<div class="card mb-0 h-[95vh]" v-else>
<div class="flex justify-between items-center relative">
<BackIcon class="z-2" @click="isAdd ? $router.back():(isEdit = false)"/>
<div class="absolute text-2xl text-align-center w-full">{{
runtimeStore.editDict.id ? '修改' : '创建'
}}书籍
<div class="absolute text-2xl text-align-center w-full">{{ runtimeStore.editDict.id ? '修改' : '创建' }}书籍
</div>
</div>
<div class="center">

View File

@@ -0,0 +1,86 @@
<script setup lang="ts">
import "vue-activity-calendar/style.css";
import {useNav} from "@/utils";
import BasePage from "@/pages/pc/components/BasePage.vue";
import {DictResource, getDefaultDict} from "@/types.ts";
import {useRuntimeStore} from "@/stores/runtime.ts";
import BaseIcon from "@/components/BaseIcon.vue";
import Empty from "@/components/Empty.vue";
import Input from "@/pages/pc/components/Input.vue";
import BaseButton from "@/components/BaseButton.vue";
import DictList from "@/pages/pc/components/list/DictList.vue";
import BackIcon from "@/pages/pc/components/BackIcon.vue";
import {useRouter} from "vue-router";
import {enArticle} from "@/assets/dictionary.ts";
import {computed} from "vue";
const {nav} = useNav()
const runtimeStore = useRuntimeStore()
const router = useRouter()
function selectDict(e) {
console.log(e.dict)
getDictDetail(e.dict)
}
async function getDictDetail(val: DictResource) {
runtimeStore.editDict = getDefaultDict(val)
nav('book-detail', {from: 'list'})
}
let showSearchInput = $ref(false)
let searchKey = $ref('')
const searchList = computed<any[]>(() => {
if (searchKey) {
let s = searchKey.toLowerCase()
return enArticle.filter((item) => {
return item.id.toLowerCase().includes(s)
|| item.name.toLowerCase().includes(s)
|| item.category.toLowerCase().includes(s)
|| item.tags.join('').replace('所有', '').toLowerCase().includes(s)
|| item?.url?.toLowerCase?.().includes?.(s)
})
}
return []
})
</script>
<template>
<BasePage>
<div class="card">
<div class="flex items-center relative gap-2">
<BackIcon class="z-2" @Click='router.back()'/>
<div class="flex flex-1 gap-4" v-if="showSearchInput">
<Input placeholder="请输入书籍名称/缩写/类别" v-model="searchKey" class="flex-1" autofocus/>
<BaseButton @click="showSearchInput = false, searchKey = ''">取消</BaseButton>
</div>
<div class="py-1 flex flex-1 justify-end" v-else>
<span class="page-title absolute w-full center">书籍列表</span>
<BaseIcon @click="showSearchInput = true"
class="z-1"
icon="fluent:search-24-regular"/>
</div>
</div>
<div class="mt-4" v-if="searchKey">
<DictList
v-if="searchList.length "
@selectDict="selectDict"
:list="searchList"
:select-id="'-1'"/>
<Empty v-else text="没有相关书籍"/>
</div>
<div class="w-full mt-2" v-else>
<DictList
v-if="enArticle.length "
@selectDict="selectDict"
:list="enArticle"
:select-id="'-1'"/>
</div>
</div>
</BasePage>
</template>
<style scoped lang="scss">
</style>

View File

@@ -9,7 +9,7 @@ import {genArticleSectionData, splitCNArticle2, splitEnArticle2, usePlaySentence
import {_nextTick, _parseLRC, cloneDeep, last} from "@/utils";
import {watch} from "vue";
import Empty from "@/components/Empty.vue";
import {ElInputNumber, ElOption, ElPopover, ElSelect, ElUpload, UploadProps} from "element-plus";
import {ElInputNumber, ElMessage, ElOption, ElPopover, ElSelect, ElUpload, UploadProps} from "element-plus";
import * as Comparison from "string-comparison"
import BaseIcon from "@/components/BaseIcon.vue";
import Dialog from "@/pages/pc/components/dialog/Dialog.vue";

View File

@@ -1,310 +0,0 @@
<script setup lang="ts">
import {onMounted, onUnmounted} from "vue";
import {Article, getDefaultArticle} from "@/types.ts";
import BaseButton from "@/components/BaseButton.vue";
import {cloneDeep} from "@/utils";
import {useBaseStore} from "@/stores/base.ts";
import List from "@/pages/pc/components/list/List.vue";
import Dialog from "@/pages/pc/components/dialog/Dialog.vue";
import {emitter, EventKey} from "@/utils/eventBus.ts";
import {useDisableEventListener, useWindowClick} from "@/hooks/event.ts";
import {MessageBox} from "@/utils/MessageBox.tsx";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {nanoid} from "nanoid";
import MiniDialog from "@/pages/pc/components/dialog/MiniDialog.vue";
import EditArticle2 from "@/pages/pc/article/components/EditArticle2.vue";
import {_nextTick} from "@/utils";
const emit = defineEmits<{
importData: [val: Event]
exportData: [val: string]
}>()
const base = useBaseStore()
const runtimeStore = useRuntimeStore()
let article = $ref<Article>(getDefaultArticle())
let show = $ref(false)
let editArticleRef: any = $ref()
let listEl: any = $ref()
onMounted(() => {
emitter.on(EventKey.openArticleListModal, (val: Article) => {
console.log('val', val)
show = true
if (val) {
article = cloneDeep(val)
}
})
})
onUnmounted(() => {
emitter.off(EventKey.openArticleListModal)
})
useDisableEventListener(() => show)
async function selectArticle(item: Article) {
let r = await checkDataChange()
if (r) {
article = cloneDeep(item)
}
}
function checkDataChange() {
return new Promise(resolve => {
let editArticle: Article = editArticleRef.getEditArticle()
if (editArticle.id !== '-1') {
editArticle.title = editArticle.title.trim()
editArticle.titleTranslate = editArticle.titleTranslate.trim()
editArticle.text = editArticle.text.trim()
editArticle.textTranslate = editArticle.textTranslate.trim()
if (
editArticle.title !== article.title ||
editArticle.titleTranslate !== article.titleTranslate ||
editArticle.text !== article.text ||
editArticle.textTranslate !== article.textTranslate
) {
return MessageBox.confirm(
'检测到数据有变动,是否保存?',
'提示',
async () => {
let r = await editArticleRef.save('save')
if (r) resolve(true)
},
() => resolve(true),
)
}
} else {
if (editArticle.title.trim() && editArticle.text.trim()) {
return MessageBox.confirm(
'检测到数据有变动,是否保存?',
'提示',
async () => {
let r = await editArticleRef.save('save')
if (r) resolve(true)
},
() => resolve(true),
)
}
}
resolve(true)
})
}
async function add() {
let r = await checkDataChange()
if (r) {
article = getDefaultArticle()
}
}
function saveArticle(val: Article): boolean {
console.log('saveArticle', val)
if (val.id) {
let rIndex = runtimeStore.editDict.articles.findIndex(v => v.id === val.id)
if (rIndex > -1) {
runtimeStore.editDict.articles[rIndex] = cloneDeep(val)
}
} else {
let has = runtimeStore.editDict.articles.find((item: Article) => item.title === val.title)
if (has) {
ElMessage.error('已存在同名文章!')
return false
}
val.id = nanoid(6)
runtimeStore.editDict.articles.push(val)
setTimeout(() => {
listEl.scrollBottom()
})
}
article = cloneDeep(val)
//TODO 保存完成后滚动到对应位置
ElMessage.success('保存成功!')
syncBookInMyStudyList()
return true
}
//todo 考虑与syncDictInMyStudyList、changeDict方法合并
function syncBookInMyStudyList(study = false) {
_nextTick(() => {
let rIndex = base.article.bookList.findIndex(v => v.id === runtimeStore.editDict.id)
let temp = cloneDeep(runtimeStore.editDict);
console.log(temp)
temp.custom = true
temp.length = temp.articles.length
if (rIndex > -1) {
base.article.bookList[rIndex] = temp
if (study) base.article.studyIndex = rIndex
} else {
base.article.bookList.push(temp)
if (study) base.article.studyIndex = base.article.bookList.length - 1
}
}, 100)
}
function saveAndNext(val: Article) {
if (saveArticle(val)) {
add()
}
}
let showExport = $ref(false)
useWindowClick(() => showExport = false)
</script>
<template>
<Dialog
v-model="show"
:full-screen="true"
:header="false"
>
<div class="add-article">
<div class="aslide">
<header>
<div class="dict-name">{{ runtimeStore.editDict.name }}</div>
</header>
<List
ref="listEl"
v-model:list="runtimeStore.editDict.articles"
:select-item="article"
@del-select-item="article = getDefaultArticle()"
@select-item="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="add" v-if="!article.title">
正在添加新文章...
</div>
<div class="footer">
<div class="import">
<BaseButton size="small">导入</BaseButton>
<input type="file"
accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
@change="e => emit('importData',e)">
</div>
<div class="export"
style="position: relative"
@click.stop="null">
<BaseButton size="small" @click="showExport = true">导出</BaseButton>
<MiniDialog
v-model="showExport"
style="width: 80rem;bottom: calc(100% + 10rem);top:unset;"
>
<div class="mini-row-title">
导出选项
</div>
<div class="mini-row">
<BaseButton size="small" @click="emit('exportData',{type:'all',data:[]})">全部文章</BaseButton>
</div>
<div class="mini-row">
<BaseButton size="small" @click="emit('exportData',{type:'chapter',data:article})">当前章节</BaseButton>
</div>
</MiniDialog>
</div>
<BaseButton size="small" @click="add">新增</BaseButton>
</div>
</div>
<EditArticle2
ref="editArticleRef"
type="batch"
@save="saveArticle"
@saveAndNext="saveAndNext"
:article="article"/>
</div>
</Dialog>
</template>
<style scoped lang="scss">
.add-article {
//position: fixed;
position: relative;
left: 0;
top: 0;
z-index: 9;
width: 100%;
height: 100%;
box-sizing: border-box;
color: var(--color-font-1);
background: var(--color-second);
display: flex;
.close {
position: absolute;
right: 1.2rem;
top: 1.2rem;
}
.aslide {
width: 14vw;
height: 100%;
padding: 0 .6rem;
display: flex;
flex-direction: column;
$height: 4rem;
header {
height: $height;
display: flex;
justify-content: space-between;
align-items: center;
//opacity: 0;
.dict-name {
font-size: 2rem;
color: var(--color-font-1);
}
}
.name {
font-size: 1.1rem;
}
.translate-name {
font-size: 1rem;
}
.add {
width: 16rem;
box-sizing: border-box;
border-radius: .5rem;
margin-bottom: .6rem;
padding: .6rem;
display: flex;
justify-content: space-between;
transition: all .3s;
color: var(--color-font-1);
background: var(--color-item-active);
}
.footer {
height: $height;
display: flex;
gap: .6rem;
align-items: center;
justify-content: flex-end;
.import {
display: inline-flex;
position: relative;
input {
position: absolute;
height: 100%;
width: 100%;
opacity: 0;
}
}
}
}
}
</style>

View File

@@ -3,7 +3,7 @@
import {Dict, DictType, getDefaultDict} from "@/types.ts";
import {cloneDeep} from "@/utils";
import {ElForm,ElFormItem,ElInput,ElSelect,ElOption, FormInstance, FormRules} from "element-plus";
import {ElForm, ElFormItem, ElInput, ElSelect, ElOption, FormInstance, FormRules, ElMessage} from "element-plus";
import {onMounted, reactive} from "vue";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {useBaseStore} from "@/stores/base.ts";

View File

@@ -1,10 +1,10 @@
<script setup lang="ts">
import {Dict} from "@/types.ts";
import {Dict, DictResource} from "@/types.ts";
import {Icon} from "@iconify/vue";
import {ElProgress, ElCheckbox} from 'element-plus';
const props = defineProps<{
item?: Dict
item?: Partial<Dict>;
quantifier?: string
isAdd: boolean
showCheckbox?: boolean
@@ -16,7 +16,7 @@ defineEmits<{
}>()
const progress = $computed(() => {
if (props.item.complete) return 100
if (props.item?.complete) return 100
return Number(((props.item?.lastLearnIndex / props.item?.length) * 100).toFixed())
})

View File

@@ -322,7 +322,7 @@ $header-height: 4rem;
.content {
width: 25rem;
color: var(--color-font-1);
color: var(--color-main-text);
padding: .2rem 1.6rem 1.6rem;
}
}

View File

@@ -3,7 +3,7 @@ import {Dict} from "@/types.ts";
import Book from "@/pages/pc/components/Book.vue";
defineProps<{
list?: Dict[],
list?: Partial<Dict>[],
selectId?: string
}>()

View File

@@ -3,22 +3,21 @@ import type {Word} from "@/types";
import {DictId, getDefaultDict} from "@/types";
import BasePage from "@/pages/pc/components/BasePage.vue";
import {computed, onMounted, reactive} from "vue";
import {computed, onMounted, reactive, shallowReactive} from "vue";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {assign, cloneDeep} from "@/utils";
import {_getDictDataByUrl, _nextTick, cloneDeep, convertToWord} from "@/utils";
import {nanoid} from "nanoid";
import BaseIcon from "@/components/BaseIcon.vue";
import BaseTable from "@/pages/pc/components/BaseTable.vue";
import WordItem from "@/pages/pc/components/WordItem.vue";
import type {FormInstance, FormRules} from "element-plus";
import {ElForm, ElFormItem, ElInput, ElMessage} from "element-plus";
import PopConfirm from "@/pages/pc/components/PopConfirm.vue";
import BackIcon from "@/pages/pc/components/BackIcon.vue";
import BaseButton from "@/components/BaseButton.vue";
import {useRoute, useRouter} from "vue-router";
import {useBaseStore} from "@/stores/base.ts";
import EditBook from "@/pages/pc/article/components/EditBook.vue";
import {_getDictDataByUrl, _nextTick, convertToWord} from "@/utils";
import {ElForm, ElFormItem, ElInput, ElMessage} from "element-plus";
const runtimeStore = useRuntimeStore()
const base = useBaseStore()
@@ -32,7 +31,7 @@ let list = $computed({
return runtimeStore.editDict.words
},
set(v) {
runtimeStore.editDict.words = v
runtimeStore.editDict.words = shallowReactive(v)
}
})
@@ -89,7 +88,7 @@ async function onSubmitWord() {
if (data.id) {
let r = list.find(v => v.id === data.id)
if (r) {
assign(r, data)
Object.assign(r, data)
ElMessage.success('修改成功')
} else {
ElMessage.success('修改失败,未找到单词')
@@ -335,13 +334,13 @@ defineRender(() => {
</ElFormItem>
</ElForm>
<div class="center">
<base-button
<BaseButton
type="info"
onClick={closeWordForm}>关闭
</base-button>
<base-button type="primary"
onClick={onSubmitWord}>保存
</base-button>
</BaseButton>
<BaseButton type="primary"
onClick={onSubmitWord}>保存
</BaseButton>
</div>
</div>
) : null

View File

@@ -62,7 +62,8 @@ const searchList = computed<any[]>(() => {
if (searchKey) {
let s = searchKey.toLowerCase()
return dictionaryResources.filter((item) => {
return item.name.toLowerCase().includes(s)
return item.id.toLowerCase().includes(s)
|| item.name.toLowerCase().includes(s)
|| item.category.toLowerCase().includes(s)
|| item.tags.join('').replace('所有', '').toLowerCase().includes(s)
|| item?.url?.toLowerCase?.().includes?.(s)

View File

@@ -14,6 +14,7 @@ import BookDetail from "@/pages/pc/article/BookDetail.vue";
import BatchEditArticlePage from "@/pages/pc/article/BatchEditArticlePage.vue";
import DictList from "@/pages/pc/word/DictList.vue";
import Setting from "@/pages/pc/Setting.vue";
import BookList from "@/pages/pc/article/BookList.vue";
export const routes: RouteRecordRaw[] = [
{
@@ -25,28 +26,16 @@ export const routes: RouteRecordRaw[] = [
{path: 'dict-list', component: DictList},
{path: 'study-word', component: StudyWord},
{path: 'dict-detail', component: DictDetail},
{path: 'article', component: ArticleHomePage},
{path: 'study-article', component: StudyArticle},
{path: 'edit-article', component: EditArticlePage},
{path: 'batch-edit-article', component: BatchEditArticlePage},
{path: 'book-detail', component: BookDetail},
{path: 'book-list', component: BookList},
{path: 'setting', component: Setting},
]
},
// {path: '/mobile', component: Mobile,},
// {path: '/mobile/practice', component: MobilePractice},
// {path: '/mobile/dict-detail', component: DictDetail},
// {path: '/mobile/set-dict-plan', name: 'set-dict-plan', component: SetDictPlan},
// {path: '/mobile/setting', component: Setting},
// {path: '/mobile/music-setting', component: MusicSetting},
// {path: '/mobile/other-setting', component: OtherSetting},
// {path: '/mobile/data-manage', component: DataManage},
// {path: '/mobile/collect', component: CollectPage},
// {path: '/mobile/wrong', component: WrongPage},
// {path: '/mobile/simple', component: SimplePage},
// {path: '/mobile/about', component: About},
// {path: '/mobile/feedback', component: Feedback},
{path: '/test', component: Test},
// {path: '/', redirect: '/pc/practice'},
]

View File

@@ -88,7 +88,10 @@ export const useBaseStore = defineStore('base', {
return getDefaultDict()
},
sdict(): Dict {
return this.currentStudyWordDict
if (this.word.studyIndex >= 0) {
return this.word.bookList[this.word.studyIndex] ?? getDefaultDict()
}
return getDefaultDict()
},
currentStudyProgress(): number {
if (!this.sdict.words?.length) return 0
@@ -98,6 +101,9 @@ export const useBaseStore = defineStore('base', {
currentBook(): Dict {
return this.article.bookList[this.article.studyIndex] ?? {}
},
sbook(): Dict {
return this.article.bookList[this.article.studyIndex] ?? {}
},
currentBookProgress(): number {
if (this.currentBook.name) return Number(Number(this.currentBook.lastLearnIndex / this.currentBook.length).toFixed(2))
return 0
@@ -131,19 +137,6 @@ export const useBaseStore = defineStore('base', {
} catch (e) {
console.error('读取本地dict数据失败', e)
}
if (this.article.studyIndex >= 1) {
let current = this.article.bookList[this.article.studyIndex]
let dictResourceUrl = `./dicts/${current.language}/${current.type}/${current.translateLanguage}/${current.url}`;
if (!current.articles.length) {
let s = await getDictFile(dictResourceUrl)
current.articles = cloneDeep(s.map(v => {
v.id = nanoid(6)
return v
}))
}
// console.log('this.currentBook', this.currentBook.articles[0])
}
resolve(true)
})
},
@@ -168,5 +161,22 @@ export const useBaseStore = defineStore('base', {
this.word.studyIndex = this.word.bookList.length - 1
}
},
//改变书籍
changeBook(val: Dict) {
//把其他的书籍里面的文章数据都删掉,全保存在内存里太卡了
this.article.bookList.slice(1).map(v => {
if (!v.custom) {
v.articles = shallowReactive([])
}
})
let rIndex = this.article.bookList.findIndex((v: Dict) => v.id === val.id)
if (rIndex > -1) {
this.article.studyIndex = rIndex
this.article.bookList[this.article.studyIndex].articles = shallowReactive(val.articles)
} else {
this.article.bookList.push(getDefaultDict(val))
this.article.studyIndex = this.article.bookList.length - 1
}
},
},
})

View File

@@ -10,6 +10,7 @@ import axios from "axios";
import {env} from "@/config/ENV.ts";
import {nextTick} from "vue";
import {dictionaryResources, enArticle} from "@/assets/dictionary.ts";
import {ElMessage} from "element-plus";
export function no() {
ElMessage.warning('未现实')
@@ -224,7 +225,7 @@ export function shakeCommonDict(n: BaseState): BaseState {
if (!v.custom) v.words = []
})
data.article.bookList.map((v: Dict) => {
if (!v.custom) v.words = []
if (!v.custom) v.articles = []
})
return data
}
@@ -233,14 +234,14 @@ export function isMobile(): boolean {
return /Mobi|Android|iPhone/i.test(navigator.userAgent)
}
export function getDictFile(url: string) {
return new Promise<any[]>(async resolve => {
let r = await fetch(url).catch(r => {
console.log('getDictFile_error', r)
})
let v = await r.json()
resolve(v)
})
export async function getDictFile(url: string) {
try {
const r = await fetch(url);
return await r.json();
} catch (err) {
console.log('getDictFile_error', err);
return null;
}
}
export function useNav() {
@@ -391,17 +392,29 @@ export function _parseLRC(lrc: string): { start: number, end: number, text: stri
return parsed;
}
export async function _getDictDataByUrl(val: DictResource): Promise<Dict> {
export async function _getDictDataByUrl(val: DictResource, type: DictType = DictType.word): Promise<Dict> {
let dictResourceUrl = `./dicts/${val.language}/word/${val.url}`.replace('.json', '_v2.json');
if (type === DictType.article) {
dictResourceUrl = `./dicts/${val.language}/${val.type}/${val.url}`;
}
let s = await getDictFile(dictResourceUrl)
let words = cloneDeep(s.map(v => {
v.id = nanoid(6)
return v
}))
return getDefaultDict({
...val,
words
})
if (s) {
if (type === DictType.word) {
let words = cloneDeep(s.map(v => {
v.id = nanoid(6)
return v
}))
return getDefaultDict({...val, words})
} else {
let articles = cloneDeep(s.map(v => {
v.id = nanoid(6)
return v
}))
console.log('articles',articles)
return getDefaultDict({...val, articles})
}
}
return getDefaultDict()
}
//从字符串里面转换为Word格式
@@ -555,10 +568,6 @@ export function reverse<T>(array: T[]): T[] {
return array.slice().reverse();
}
export function assign<T extends object, U extends object>(target: T, ...sources: U[]): T & U {
return Object.assign(target, ...sources);
}
export function groupBy<T extends Record<string, any>>(array: T[], key: string) {
return array.reduce<Record<string, T[]>>((result, item) => {
const groupKey = String(item[key]);