feat:重构词典
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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">
|
||||
|
||||
86
src/pages/pc/article/BookList.vue
Normal file
86
src/pages/pc/article/BookList.vue
Normal 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>
|
||||
@@ -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";
|
||||
|
||||
@@ -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>
|
||||
@@ -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";
|
||||
|
||||
@@ -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())
|
||||
})
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}>()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user