feat:重构代码
This commit is contained in:
@@ -6,103 +6,145 @@ 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 "lodash-es";
|
||||
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";
|
||||
|
||||
const {nav} = useNav()
|
||||
const base = useBaseStore()
|
||||
const router = useRouter()
|
||||
const store = useBaseStore()
|
||||
const runtimeStore = useRuntimeStore()
|
||||
let showAddChooseDialog = $ref(false)
|
||||
let showSearchDialog = $ref(false)
|
||||
let searchKey = $ref('')
|
||||
|
||||
function clickEvent(e) {
|
||||
console.log('e', e)
|
||||
}
|
||||
|
||||
async function getBookDetail(val: DictResource) {
|
||||
let r = await getArticleBookDataByUrl(val)
|
||||
runtimeStore.editDict = cloneDeep(r)
|
||||
nav('book-detail')
|
||||
}
|
||||
|
||||
async function getBookDetail2(val: Dict) {
|
||||
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})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BasePage>
|
||||
<div class="flex gap-6">
|
||||
<div class="card w-1/4 flex flex-col">
|
||||
<div class="title">
|
||||
我的词典
|
||||
<div class="card ">
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="bg-slate-200 p-3 gap-4 rounded-md cursor-pointer flex items-center">
|
||||
<span class="text-lg font-bold">{{ base.currentArticleDict.name }}</span>
|
||||
<BaseIcon @click="showSearchDialog = true" icon="gg:arrows-exchange"/>
|
||||
</div>
|
||||
<div class="grid flex-1 flex gap-5 mt-4">
|
||||
<div class="p-4 flex-1 rounded-md bg-slate-200 relative">
|
||||
<span>收藏</span>
|
||||
<div class="absolute bottom-4 right-4">3篇</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid flex-1 flex gap-5 mt-4" @click="router.push('edit-article')">
|
||||
<div class="p-4 flex-1 rounded-md bg-slate-200 relative">
|
||||
<span>添加</span>
|
||||
<div class="absolute bottom-4 right-4">3篇</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-3/4">
|
||||
<div class="card ">
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="bg-slate-200 p-3 rounded-md cursor-pointer flex items-center">
|
||||
<span class="text-lg font-bold">{{ base.currentArticleDict.name }}</span>
|
||||
<Icon icon="gg:arrows-exchange" class="text-2xl ml-2"/>
|
||||
<Icon icon="uil:setting" class="text-2xl ml-2"/>
|
||||
</div>
|
||||
<div class="rounded-xl bg-slate-800 flex items-center py-3 px-5 text-white cursor-pointer"
|
||||
@click="router.push('/learn-article')">
|
||||
开始学习
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 text-sm">已学习5555个单词的1%</div>
|
||||
<el-progress class="mt-1" :percentage="80" :show-text="false"></el-progress>
|
||||
<div class="rounded-xl bg-slate-800 flex items-center py-3 px-5 text-white cursor-pointer"
|
||||
@click="router.push('/learn-article')">
|
||||
开始学习
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 text-sm">已学习5555个单词的1%</div>
|
||||
<el-progress class="mt-1" :percentage="80" :show-text="false"></el-progress>
|
||||
</div>
|
||||
|
||||
<div class="card flex flex-col">
|
||||
<div class="title">
|
||||
我的词典
|
||||
我的
|
||||
</div>
|
||||
<div class="grid grid-cols-6 gap-4 mt-4">
|
||||
<div class="my-dict" @click="nav('edit-word-dict',{type:0})">
|
||||
<span>收藏</span>
|
||||
<div class="absolute bottom-4 right-4">{{ store.collectWord.words.length }}个词</div>
|
||||
<div class="book"
|
||||
v-for="dict in store.article.bookList"
|
||||
@click="getBookDetail2(dict)">
|
||||
<div class="name">{{ dict.name }}</div>
|
||||
<div class="desc">{{ dict.description }}</div>
|
||||
<div class="absolute bottom-4 right-4">{{ dict.length }}篇</div>
|
||||
</div>
|
||||
<div class="my-dict" @click="nav('edit-word-dict',{type:1})">
|
||||
<span>错词本</span>
|
||||
<div class="absolute bottom-4 right-4">{{ store.wrong.words.length }}个词</div>
|
||||
<div class="book" @click="showAddChooseDialog = true">
|
||||
<div class="center h-full">
|
||||
<Icon
|
||||
width="40px"
|
||||
icon="fluent:add-20-filled"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<div class="title">文章</div>
|
||||
<div class="mt-4 flex gap-4">
|
||||
<div
|
||||
class="bg-white rounded-md p-4 h-40 w-30 relative cursor-pointer"
|
||||
v-for="dict in enArticle"
|
||||
>
|
||||
<div class="top">
|
||||
<div class="name">{{ dict.name }}</div>
|
||||
<div class="desc">{{ dict.description }}</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="title flex justify-between">
|
||||
<span>书籍列表</span>
|
||||
<BaseIcon @click="showSearchDialog = true" icon="fluent:search-24-regular"/>
|
||||
</div>
|
||||
<div class="grid grid-cols-6 gap-4 mt-4">
|
||||
<div class="book"
|
||||
v-for="dict in enArticle"
|
||||
@click="getBookDetail(dict)">
|
||||
<div class="name">{{ dict.name }}</div>
|
||||
<div class="desc">{{ dict.description }}</div>
|
||||
<div class="absolute bottom-4 right-4">{{ dict.length }}篇</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Dialog v-model="showAddChooseDialog" title="选项">
|
||||
<div class="color-black px-6 w-100">
|
||||
<div class="cursor-pointer hover:bg-black/10 p-2 rounded"
|
||||
@click="showAddChooseDialog = false,showSearchDialog = true">选择一本书籍
|
||||
</div>
|
||||
<p class="cursor-pointer hover:bg-black/10 p-2 rounded" @click="addBook">创建自己的书籍</p>
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
<Dialog v-model="showSearchDialog"
|
||||
:show-close="false"
|
||||
@close="searchKey = ''"
|
||||
:header="false">
|
||||
<div class="color-black w-140">
|
||||
<div class="p-4">
|
||||
<Input v-if="showSearchDialog" :autofocus="true" v-model="searchKey"/>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div v-if="searchList.length">
|
||||
<div class="p-4 min-h-40 max-h-140 overflow-auto">
|
||||
<div class="flex justify-between my-2 hover:bg-black/10 p-2 rounded"
|
||||
v-for="dict in searchList"
|
||||
@click="getBookDetail(dict)">
|
||||
<div class="name">{{ dict.name }}</div>
|
||||
<div class="">{{ dict.length }}篇</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="h-40 center flex-col text-xl color-black/60">
|
||||
<div> 请输入书籍名称搜索</div>
|
||||
<div>或直接在书籍列表选中</div>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</BasePage>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.card {
|
||||
@apply rounded-xl bg-white p-4 mt-5;
|
||||
}
|
||||
|
||||
.center {
|
||||
@apply flex justify-center items-center;
|
||||
}
|
||||
|
||||
.title {
|
||||
@apply text-lg font-medium;
|
||||
}
|
||||
.my-dict {
|
||||
@apply p-4 rounded-md bg-slate-200 relative cursor-pointer h-40;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import Toolbar from "@/pages/pc/components/toolbar/index.vue"
|
||||
import {onMounted, onUnmounted, watch} from "vue";
|
||||
import {usePracticeStore} from "@/stores/practice.ts";
|
||||
import Footer from "@/pages/pc/word/Footer.vue";
|
||||
import {useBaseStore} from "@/stores/base.ts";
|
||||
|
||||
import Statistics from "@/pages/pc/word/Statistics.vue";
|
||||
import {emitter, EventKey} from "@/utils/eventBus.ts";
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import {useRuntimeStore} from "@/stores/runtime.ts";
|
||||
import {MessageBox} from "@/utils/MessageBox.tsx";
|
||||
import PracticeArticle from "@/pages/pc/practice/practice-article/index.vue";
|
||||
import {ShortcutKey} from "@/types.ts";
|
||||
import DictModal from "@/pages/pc/components/dialog/DictDiglog.vue";
|
||||
import {useStartKeyboardEventListener} from "@/hooks/event.ts";
|
||||
import useTheme from "@/hooks/theme.ts";
|
||||
|
||||
const statisticsStore = usePracticeStore()
|
||||
const store = useBaseStore()
|
||||
const settingStore = useSettingStore()
|
||||
const runtimeStore = useRuntimeStore()
|
||||
const {toggleTheme} = useTheme()
|
||||
const practiceRef: any = $ref()
|
||||
|
||||
watch(statisticsStore, () => {
|
||||
if (statisticsStore.inputWordNumber < 1) {
|
||||
return statisticsStore.correctRate = -1
|
||||
}
|
||||
if (statisticsStore.wrong > statisticsStore.inputWordNumber) {
|
||||
return statisticsStore.correctRate = 0
|
||||
}
|
||||
statisticsStore.correctRate = 100 - Math.trunc(((statisticsStore.wrong) / (statisticsStore.inputWordNumber)) * 100)
|
||||
})
|
||||
|
||||
|
||||
function test() {
|
||||
MessageBox.confirm(
|
||||
'2您选择了“本地翻译”,但译文内容却为空白,是否修改为“不需要翻译”并保存?',
|
||||
'1提示',
|
||||
() => {
|
||||
console.log('ok')
|
||||
},
|
||||
() => {
|
||||
console.log('cencal')
|
||||
})
|
||||
}
|
||||
|
||||
function write() {
|
||||
// console.log('write')
|
||||
settingStore.dictation = true
|
||||
repeat()
|
||||
}
|
||||
|
||||
//TODO 需要判断是否已忽略
|
||||
function repeat() {
|
||||
// console.log('repeat')
|
||||
emitter.emit(EventKey.resetWord)
|
||||
practiceRef.getCurrentPractice()
|
||||
}
|
||||
|
||||
function prev() {
|
||||
// console.log('next')
|
||||
if (store.currentDict.chapterIndex === 0) {
|
||||
ElMessage.warning('已经在第一章了~')
|
||||
} else {
|
||||
store.currentDict.chapterIndex--
|
||||
repeat()
|
||||
}
|
||||
}
|
||||
|
||||
function toggleShowTranslate() {
|
||||
settingStore.translate = !settingStore.translate
|
||||
}
|
||||
|
||||
function toggleDictation() {
|
||||
settingStore.dictation = !settingStore.dictation
|
||||
}
|
||||
|
||||
function openSetting() {
|
||||
runtimeStore.showSettingModal = true
|
||||
}
|
||||
|
||||
function openDictDetail() {
|
||||
emitter.emit(EventKey.openDictModal, 'detail')
|
||||
}
|
||||
|
||||
function toggleConciseMode() {
|
||||
settingStore.showToolbar = !settingStore.showToolbar
|
||||
settingStore.showPanel = settingStore.showToolbar
|
||||
}
|
||||
|
||||
function togglePanel() {
|
||||
settingStore.showPanel = !settingStore.showPanel
|
||||
}
|
||||
|
||||
function jumpSpecifiedChapter(val: number) {
|
||||
store.currentDict.chapterIndex = val
|
||||
repeat()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
emitter.on(EventKey.write, write)
|
||||
emitter.on(EventKey.repeat, repeat)
|
||||
emitter.on(EventKey.jumpSpecifiedChapter, jumpSpecifiedChapter)
|
||||
|
||||
emitter.on(ShortcutKey.PreviousChapter, prev)
|
||||
emitter.on(ShortcutKey.RepeatChapter, repeat)
|
||||
emitter.on(ShortcutKey.DictationChapter, write)
|
||||
emitter.on(ShortcutKey.ToggleShowTranslate, toggleShowTranslate)
|
||||
emitter.on(ShortcutKey.ToggleDictation, toggleDictation)
|
||||
emitter.on(ShortcutKey.OpenSetting, openSetting)
|
||||
emitter.on(ShortcutKey.OpenDictDetail, openDictDetail)
|
||||
emitter.on(ShortcutKey.ToggleTheme, toggleTheme)
|
||||
emitter.on(ShortcutKey.ToggleConciseMode, toggleConciseMode)
|
||||
emitter.on(ShortcutKey.TogglePanel, togglePanel)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
emitter.off(EventKey.write, write)
|
||||
emitter.off(EventKey.repeat, repeat)
|
||||
emitter.off(EventKey.jumpSpecifiedChapter, jumpSpecifiedChapter)
|
||||
|
||||
emitter.off(ShortcutKey.PreviousChapter, prev)
|
||||
emitter.off(ShortcutKey.RepeatChapter, repeat)
|
||||
emitter.off(ShortcutKey.DictationChapter, write)
|
||||
emitter.off(ShortcutKey.ToggleShowTranslate, toggleShowTranslate)
|
||||
emitter.off(ShortcutKey.ToggleDictation, toggleDictation)
|
||||
emitter.off(ShortcutKey.OpenSetting, openSetting)
|
||||
emitter.off(ShortcutKey.OpenDictDetail, openDictDetail)
|
||||
emitter.off(ShortcutKey.ToggleTheme, toggleTheme)
|
||||
emitter.off(ShortcutKey.ToggleConciseMode, toggleConciseMode)
|
||||
emitter.off(ShortcutKey.TogglePanel, togglePanel)
|
||||
})
|
||||
|
||||
useStartKeyboardEventListener()
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div class="practice-wrapper">
|
||||
<Toolbar/>
|
||||
<PracticeArticle ref="practiceRef"/>
|
||||
<Footer/>
|
||||
</div>
|
||||
<DictModal/>
|
||||
<Statistics/>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.practice-wrapper {
|
||||
font-size: 0.9rem;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
//padding-right: var(--practice-wrapper-padding-right);
|
||||
transform: translateX(var(--practice-wrapper-translateX));
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
277
src/pages/pc/article/BatchEditArticlePage.vue
Normal file
277
src/pages/pc/article/BatchEditArticlePage.vue
Normal file
@@ -0,0 +1,277 @@
|
||||
<script setup lang="ts">
|
||||
import {onMounted, onUnmounted} from "vue";
|
||||
import {Article, DefaultArticle} from "@/types.ts";
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
import {cloneDeep} from "lodash-es";
|
||||
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 {syncMyDictList} from "@/hooks/dict.ts";
|
||||
import MiniDialog from "@/pages/pc/components/dialog/MiniDialog.vue";
|
||||
import EditArticle2 from "@/pages/pc/article/components/EditArticle2.vue";
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
|
||||
const emit = defineEmits<{
|
||||
importData: [val: Event]
|
||||
exportData: [val: string]
|
||||
}>()
|
||||
const base = useBaseStore()
|
||||
const runtimeStore = useRuntimeStore()
|
||||
|
||||
let article = $ref<Article>(cloneDeep(DefaultArticle))
|
||||
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 = cloneDeep(DefaultArticle)
|
||||
}
|
||||
}
|
||||
|
||||
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('保存成功!')
|
||||
syncMyDictList(runtimeStore.editDict)
|
||||
return true
|
||||
}
|
||||
|
||||
function saveAndNext(val: Article) {
|
||||
if (saveArticle(val)) {
|
||||
add()
|
||||
}
|
||||
}
|
||||
|
||||
let showExport = $ref(false)
|
||||
useWindowClick(() => showExport = false)
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="add-article">
|
||||
<div class="aslide">
|
||||
<header class="flex justify-between items-center">
|
||||
<BaseIcon
|
||||
title="返回"
|
||||
@click="$router.back"
|
||||
icon="formkit:left"/>
|
||||
<div class="text-xl">{{ runtimeStore.editDict.name }}</div>
|
||||
</header>
|
||||
<List
|
||||
ref="listEl"
|
||||
v-model:list="runtimeStore.editDict.articles"
|
||||
:select-item="article"
|
||||
@del-select-item="article = cloneDeep(DefaultArticle)"
|
||||
@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>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
|
||||
.add-article {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
box-sizing: border-box;
|
||||
color: var(--color-font-1);
|
||||
background: var(--color-second-bg);
|
||||
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;
|
||||
}
|
||||
|
||||
.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>
|
||||
126
src/pages/pc/article/BookDetail.vue
Normal file
126
src/pages/pc/article/BookDetail.vue
Normal file
@@ -0,0 +1,126 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import BasePage from "@/pages/pc/components/BasePage.vue";
|
||||
import BackIcon from "@/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, DefaultArticle} from "@/types.ts";
|
||||
import {cloneDeep} from "lodash-es";
|
||||
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";
|
||||
|
||||
const runtimeStore = useRuntimeStore()
|
||||
const base = useBaseStore()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
let isEdit = $ref(false)
|
||||
let isAdd = $ref(false)
|
||||
|
||||
let article: Article = $ref(cloneDeep(DefaultArticle))
|
||||
let chapterIndex = $ref(-1)
|
||||
|
||||
function handleCheckedChange(val) {
|
||||
let rIndex = runtimeStore.editDict.articles.findIndex(v => v.id === val.item.id)
|
||||
if (rIndex > -1) {
|
||||
chapterIndex = rIndex
|
||||
article = val.item
|
||||
}
|
||||
}
|
||||
|
||||
const activeId = $computed(() => {
|
||||
return runtimeStore.editDict.articles?.[chapterIndex]?.id ?? ''
|
||||
})
|
||||
|
||||
function addMyBookList() {
|
||||
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
|
||||
}
|
||||
router.back()
|
||||
}
|
||||
|
||||
const showBookDetail = computed(() => {
|
||||
return !(isAdd || isEdit);
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (route.query?.isAdd) {
|
||||
isAdd = true
|
||||
}
|
||||
})
|
||||
|
||||
function formClose() {
|
||||
if (isEdit) isEdit = false
|
||||
else router.back()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BasePage>
|
||||
<div class="card mb-0 h-[95vh] flex flex-col" v-if="showBookDetail">
|
||||
<div class="flex justify-between items-center relative">
|
||||
<BackIcon class="z-2" @click="$router.back"/>
|
||||
<div class="absolute text-2xl text-align-center w-full">{{ runtimeStore.editDict.name }}</div>
|
||||
<div class="flex gap-2">
|
||||
<BaseButton type="info" @click="isEdit = true">编辑信息</BaseButton>
|
||||
<BaseButton type="info" @click="router.push('batch-edit-article')">文章管理</BaseButton>
|
||||
<BaseButton @click="addMyBookList">学习</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-lg ">介绍:{{ runtimeStore.editDict.description }}</div>
|
||||
<div class="line my-3"></div>
|
||||
|
||||
<div class="flex flex-1 overflow-hidden">
|
||||
<div class="left flex-[2] scroll p-0">
|
||||
<ArticleList
|
||||
v-if="runtimeStore.editDict.articles.length"
|
||||
@title="handleCheckedChange"
|
||||
@click="handleCheckedChange"
|
||||
:list="runtimeStore.editDict.articles"
|
||||
:active-id="activeId">
|
||||
</ArticleList>
|
||||
<Empty v-else/>
|
||||
</div>
|
||||
<div class="right flex-[3] shrink-0 pl-4 overflow-auto">
|
||||
<div v-if="chapterIndex>-1">
|
||||
<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>
|
||||
</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>
|
||||
<Empty v-else/>
|
||||
</div>
|
||||
</div>
|
||||
<Empty v-else/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="center" v-else>
|
||||
<div class="w-1/2">
|
||||
<EditBook :is-add="isAdd"
|
||||
@close="formClose"
|
||||
@submit="isEdit = isAdd = false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</BasePage>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import EditArticle2 from "@/pages/pc/components/article/EditArticle2.vue";
|
||||
import EditArticle2 from "@/pages/pc/article/components/EditArticle2.vue";
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
</script>
|
||||
|
||||
|
||||
@@ -487,7 +487,7 @@ function setStartTime(val: Sentence, i: number, j: number) {
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex gap-2 items-center">
|
||||
<div>开始时间:</div>
|
||||
<div class="flex space-between flex-1">
|
||||
<div class="flex justify-between flex-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<el-input-number v-model="editSentence.audioPosition[0]" :precision="2" :step="0.1">
|
||||
<template #suffix>
|
||||
@@ -510,7 +510,7 @@ function setStartTime(val: Sentence, i: number, j: number) {
|
||||
</div>
|
||||
<div class="flex gap-2 items-center">
|
||||
<div>结束时间:</div>
|
||||
<div class="flex space-between flex-1">
|
||||
<div class="flex justify-between flex-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<el-input-number v-model="editSentence.audioPosition[1]" :precision="2" :step="0.1">
|
||||
<template #suffix>
|
||||
@@ -14,7 +14,7 @@ import {useRuntimeStore} from "@/stores/runtime.ts";
|
||||
import {nanoid} from "nanoid";
|
||||
import {syncMyDictList} from "@/hooks/dict.ts";
|
||||
import MiniDialog from "@/pages/pc/components/dialog/MiniDialog.vue";
|
||||
import EditArticle2 from "@/pages/pc/components/article/EditArticle2.vue";
|
||||
import EditArticle2 from "@/pages/pc/article/components/EditArticle2.vue";
|
||||
|
||||
const emit = defineEmits<{
|
||||
importData: [val: Event]
|
||||
145
src/pages/pc/article/components/EditBook.vue
Normal file
145
src/pages/pc/article/components/EditBook.vue
Normal file
@@ -0,0 +1,145 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import {Dict, DictType, getDefaultDict} from "@/types.ts";
|
||||
import {cloneDeep} from "lodash-es";
|
||||
|
||||
import {FormInstance, FormRules} from "element-plus";
|
||||
import {onMounted, reactive} from "vue";
|
||||
import {useRuntimeStore} from "@/stores/runtime.ts";
|
||||
import {useBaseStore} from "@/stores/base.ts";
|
||||
import {syncMyDictList} from "@/hooks/dict.ts";
|
||||
|
||||
const props = defineProps<{
|
||||
isAdd: boolean
|
||||
}>()
|
||||
const emit = defineEmits<{
|
||||
submit: []
|
||||
close: []
|
||||
}>()
|
||||
const runtimeStore = useRuntimeStore()
|
||||
const store = useBaseStore()
|
||||
const DefaultDictForm = {
|
||||
id: '',
|
||||
name: '',
|
||||
description: '',
|
||||
category: '',
|
||||
tags: [],
|
||||
translateLanguage: 'zh-CN',
|
||||
language: 'en',
|
||||
type: DictType.article
|
||||
}
|
||||
let dictForm: any = $ref(cloneDeep(DefaultDictForm))
|
||||
const dictFormRef = $ref<FormInstance>()
|
||||
const dictRules = reactive<FormRules>({
|
||||
name: [
|
||||
{required: true, message: '请输入名称', trigger: 'blur'},
|
||||
{max: 20, message: '名称不能超过20个字符', trigger: 'blur'},
|
||||
],
|
||||
})
|
||||
|
||||
async function onSubmit() {
|
||||
await dictFormRef.validate((valid) => {
|
||||
if (valid) {
|
||||
let data: Dict = cloneDeep({
|
||||
...getDefaultDict(),
|
||||
...dictForm,
|
||||
})
|
||||
//任意修改,都将其变为自定义词典
|
||||
data.isCustom = true
|
||||
|
||||
if (props.isAdd) {
|
||||
data.id = 'custom-dict-' + Date.now()
|
||||
//TODO 允许同名?
|
||||
if (store.article.bookList.find(v => v.name === data.name)) {
|
||||
return ElMessage.warning('已有相同名称书籍!')
|
||||
} else {
|
||||
store.article.bookList.push(data)
|
||||
runtimeStore.editDict = data
|
||||
emit('submit')
|
||||
ElMessage.success('添加成功')
|
||||
}
|
||||
} else {
|
||||
let rIndex = store.article.bookList.findIndex(v => v.id === data.id)
|
||||
if (rIndex > -1) {
|
||||
store.article.bookList[rIndex] = cloneDeep(data)
|
||||
runtimeStore.editDict = cloneDeep(data)
|
||||
emit('submit')
|
||||
ElMessage.success('修改成功')
|
||||
}else {
|
||||
ElMessage.warning('修改失败')
|
||||
}
|
||||
}
|
||||
console.log('submit!', data)
|
||||
} else {
|
||||
ElMessage.warning('请填写完整')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (!props.isAdd) {
|
||||
dictForm = cloneDeep(runtimeStore.editDict)
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="edit-dict">
|
||||
<div class="wrapper">
|
||||
<div class="common-title">{{ dictForm.id ? '修改' : '添加' }}书籍</div>
|
||||
<el-form
|
||||
ref="dictFormRef"
|
||||
:rules="dictRules"
|
||||
:model="dictForm"
|
||||
label-width="8rem">
|
||||
<el-form-item label="名称" prop="name">
|
||||
<el-input v-model="dictForm.name"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="描述">
|
||||
<el-input v-model="dictForm.description" type="textarea"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="原文语言">
|
||||
<el-select v-model="dictForm.language" placeholder="请选择选项">
|
||||
<el-option label="英语" value="en"/>
|
||||
<el-option label="德语" value="de"/>
|
||||
<el-option label="日语" value="ja"/>
|
||||
<el-option label="代码" value="code"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="译文语言">
|
||||
<el-select v-model="dictForm.translateLanguage" placeholder="请选择选项">
|
||||
<!-- <el-option label="通用" value="common"/>-->
|
||||
<el-option label="中文" value="zh-CN"/>
|
||||
<el-option label="英语" value="en"/>
|
||||
<el-option label="德语" value="de"/>
|
||||
<el-option label="日语" value="ja"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<div class="center">
|
||||
<el-button @click="emit('close')">关闭</el-button>
|
||||
<el-button type="primary" @click="onSubmit">确定</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.edit-dict {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
.wrapper {
|
||||
width: 80rem;
|
||||
}
|
||||
|
||||
.el-select {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -4,7 +4,7 @@ import {Article, DefaultArticle} from "@/types.ts";
|
||||
import {cloneDeep} from "lodash-es";
|
||||
import Dialog from "@/pages/pc/components/dialog/Dialog.vue";
|
||||
import {useDisableEventListener} from "@/hooks/event.ts";
|
||||
import EditArticle2 from "@/pages/pc/components/article/EditArticle2.vue";
|
||||
import EditArticle2 from "@/pages/pc/article/components/EditArticle2.vue";
|
||||
|
||||
interface IProps {
|
||||
article?: Article
|
||||
@@ -3,8 +3,8 @@
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex justify-center h-full">
|
||||
<div class="w-5/10 py-5">
|
||||
<div class="flex justify-center">
|
||||
<div class="w-[70vw] 2xl:w-[50vw] my-5">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
@@ -12,4 +12,4 @@
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
import {Icon} from "@iconify/vue";
|
||||
import Close from "@/components/icon/Close.vue";
|
||||
import {useDisableEventListener, useWindowClick} from "@/hooks/event.ts";
|
||||
import {watch} from "vue";
|
||||
|
||||
defineProps<{
|
||||
modelValue: string
|
||||
autofocus?: boolean
|
||||
}>()
|
||||
|
||||
defineEmits(['update:modelValue'])
|
||||
@@ -20,6 +20,14 @@ useWindowClick((e: PointerEvent) => {
|
||||
|
||||
useDisableEventListener(() => focus)
|
||||
|
||||
const vFocus = {
|
||||
mounted: (el, bind) => {
|
||||
if (bind.value) {
|
||||
el.focus()
|
||||
setTimeout(() => focus = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -31,6 +39,7 @@ useDisableEventListener(() => focus)
|
||||
width="20"/>
|
||||
<input type="text"
|
||||
:value="modelValue"
|
||||
v-focus="autofocus"
|
||||
@input="e=>$emit('update:modelValue',e.target.value)"
|
||||
>
|
||||
<transition name="fade">
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
/>
|
||||
</form>
|
||||
|
||||
<div class="flex-center items-center gap-2 mt-10">
|
||||
<div class="center items-center gap-2 mt-10">
|
||||
<button
|
||||
class="bg-green-600 text-white px-6 py-2 rounded"
|
||||
@click="submitAll"
|
||||
|
||||
@@ -158,7 +158,7 @@ async function cancel() {
|
||||
<Tooltip title="关闭">
|
||||
<Icon @click="close"
|
||||
v-if="showClose"
|
||||
class="close hvr-grow pointer"
|
||||
class="close hvr-grow cursor-pointer"
|
||||
width="24" color="#929596"
|
||||
icon="ion:close-outline"/>
|
||||
</Tooltip>
|
||||
|
||||
@@ -48,7 +48,7 @@ const speedTime = $computed(() => {
|
||||
<div id="DictDialog">
|
||||
<div class="detail">
|
||||
<div class="desc">{{ store.sdict.description }}</div>
|
||||
<div class="text flex align-center gap-2">
|
||||
<div class="text flex items-center gap-2">
|
||||
<div>总词汇: {{ store.sdict.words.length }}词</div>
|
||||
<BaseIcon icon="circum:view-list"
|
||||
@click='showAllWordModal'
|
||||
|
||||
@@ -173,6 +173,7 @@ defineExpose({scrollToBottom, scrollToItem})
|
||||
|
||||
.scroller {
|
||||
flex: 1;
|
||||
padding: 0 var(--space);
|
||||
//padding: 0 var(--space);
|
||||
padding-right: var(--space);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -7,7 +7,7 @@ import {useRuntimeStore} from "@/stores/runtime.ts";
|
||||
import {cloneDeep} from "lodash-es";
|
||||
import {Article, DefaultArticle, Dict, DictResource, DictType, getDefaultDict, Sort, TranslateType} from "@/types.ts";
|
||||
import {emitter, EventKey} from "@/utils/eventBus.ts";
|
||||
import EditBatchArticleModal from "@/pages/pc/components/article/EditBatchArticleModal.vue";
|
||||
import EditBatchArticleModal from "@/pages/pc/article/components/EditBatchArticleModal.vue";
|
||||
import {Icon} from "@iconify/vue";
|
||||
import EditDict from "@/pages/pc/dict/components/EditDict.vue";
|
||||
import {nanoid} from "nanoid";
|
||||
@@ -530,4 +530,4 @@ defineExpose({getDictDetail, add, editDict})
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -162,7 +162,7 @@ onMounted(() => {
|
||||
<el-option label="文章" :value="DictType.article"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<div class="flex-center">
|
||||
<div class="center">
|
||||
<el-button @click="closeDictForm">关闭</el-button>
|
||||
<el-button type="primary" @click="onSubmit">确定</el-button>
|
||||
</div>
|
||||
@@ -188,4 +188,4 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -488,7 +488,7 @@ defineExpose({getDictDetail, add: addWord, editDict})
|
||||
<el-form-item label="音标/发音②">
|
||||
<el-input v-model="wordForm.phonetic1"/>
|
||||
</el-form-item>
|
||||
<div class="flex-center">
|
||||
<div class="center">
|
||||
<el-button @click="closeWordForm">关闭</el-button>
|
||||
<el-button type="primary" @click="onSubmitWord">保存</el-button>
|
||||
</div>
|
||||
@@ -619,4 +619,4 @@ defineExpose({getDictDetail, add: addWord, editDict})
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -22,7 +22,9 @@ const {toggleTheme} = useTheme()
|
||||
|
||||
<template>
|
||||
<div class="layout">
|
||||
<div class="aside" :class="{'expand':settingStore.sideExpand}">
|
||||
<!-- 第一个aside 占位用-->
|
||||
<div class="aside space" :class="{'expand':settingStore.sideExpand}"></div>
|
||||
<div class="aside fixed" :class="{'expand':settingStore.sideExpand}">
|
||||
<div class="top">
|
||||
<Logo v-if="settingStore.sideExpand"/>
|
||||
<div class="row" @click="router.push('/home')">
|
||||
|
||||
@@ -469,7 +469,7 @@ let showQuestions = $ref(false)
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="flex-center">
|
||||
<div class="center">
|
||||
<BaseButton @click="showQuestions =! showQuestions">显示题目</BaseButton>
|
||||
</div>
|
||||
<div class="toggle" v-if="showQuestions">
|
||||
|
||||
@@ -6,7 +6,7 @@ import TypingWord from "@/pages/pc/components/TypingWord.vue";
|
||||
import Panel from "../../components/Panel.vue";
|
||||
import {onMounted, onUnmounted} from "vue";
|
||||
import {useBaseStore} from "@/stores/base.ts";
|
||||
import EditSingleArticleModal from "@/pages/pc/components/article/EditSingleArticleModal.vue";
|
||||
import EditSingleArticleModal from "@/pages/pc/article/components/EditSingleArticleModal.vue";
|
||||
import {usePracticeStore} from "@/stores/practice.ts";
|
||||
import {emitter, EventKey, useEvents} from "@/utils/eventBus.ts";
|
||||
import IconWrapper from "@/pages/pc/components/IconWrapper.vue";
|
||||
|
||||
@@ -252,7 +252,7 @@ defineRender(() => {
|
||||
modelValue={wordForm.phonetic1}
|
||||
onUpdate:model-value={e => wordForm.phonetic1 = e}/>
|
||||
</el-form-item>
|
||||
<div class="flex-center">
|
||||
<div class="center">
|
||||
<el-button
|
||||
onClick={closeWordForm}>关闭
|
||||
</el-button>
|
||||
@@ -272,4 +272,4 @@ defineRender(() => {
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -97,10 +97,10 @@ const isEnd = $computed(() => {
|
||||
</div>
|
||||
<div class="absolute right-5 top-20 flex flex-col gap-4">
|
||||
<Tooltip title="分享给朋友">
|
||||
<Icon class="hvr-grow pointer" icon="ph:share-light" width="20" color="#929596"/>
|
||||
<Icon class="hvr-grow cursor-pointer" icon="ph:share-light" width="20" color="#929596"/>
|
||||
</Tooltip>
|
||||
<Tooltip title="请我喝杯咖啡">
|
||||
<Icon class="hvr-grow pointer" icon="twemoji:teacup-without-handle" width="20" color="#929596"/>
|
||||
<Icon class="hvr-grow cursor-pointer" icon="twemoji:teacup-without-handle" width="20" color="#929596"/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div class="footer">
|
||||
|
||||
@@ -162,19 +162,19 @@ function changePerDayStudyNumber() {
|
||||
我的词典
|
||||
</div>
|
||||
<div class="grid grid-cols-6 gap-4 mt-4">
|
||||
<div class="my-dict" @click="nav('edit-word-dict',{type:0})">
|
||||
<div class="book" @click="nav('edit-word-dict',{type:0})">
|
||||
<span>收藏</span>
|
||||
<div class="absolute bottom-4 right-4">{{ store.collectWord.words.length }}个词</div>
|
||||
</div>
|
||||
<div class="my-dict" @click="nav('edit-word-dict',{type:1})">
|
||||
<div class="book" @click="nav('edit-word-dict',{type:1})">
|
||||
<span>错词本</span>
|
||||
<div class="absolute bottom-4 right-4">{{ store.wrong.words.length }}个词</div>
|
||||
</div>
|
||||
<div class="my-dict" @click="nav('edit-word-dict',{type:2})">
|
||||
<div class="book" @click="nav('edit-word-dict',{type:2})">
|
||||
<span>简单词</span>
|
||||
<div class="absolute bottom-4 right-4">{{ store.simple.words.length }}个词</div>
|
||||
</div>
|
||||
<div class="my-dict" @click="nav('edit-word-dict',{type:3})">
|
||||
<div class="book" @click="nav('edit-word-dict',{type:3})">
|
||||
<span>已掌握</span>
|
||||
<div class="absolute bottom-4 right-4">{{ store.master.words.length }}个词</div>
|
||||
</div>
|
||||
@@ -238,24 +238,7 @@ function changePerDayStudyNumber() {
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.card {
|
||||
@apply rounded-xl p-4 mt-5;
|
||||
background: var(--color-second-bg);
|
||||
}
|
||||
|
||||
.center {
|
||||
@apply flex justify-center items-center;
|
||||
}
|
||||
|
||||
.title {
|
||||
@apply text-lg font-medium;
|
||||
}
|
||||
|
||||
.my-dict {
|
||||
@apply p-4 rounded-md bg-slate-200 relative cursor-pointer h-40;
|
||||
}
|
||||
|
||||
.target-modal {
|
||||
.target-modal {
|
||||
width: 30rem;
|
||||
padding: var(--space);
|
||||
padding-top: 0;
|
||||
|
||||
Reference in New Issue
Block a user