This commit is contained in:
zyronon
2023-11-20 23:15:55 +08:00
parent e866f0e528
commit c98e211a7b
10 changed files with 387 additions and 25 deletions

View File

@@ -112,7 +112,7 @@ defineEmits(['click'])
border-bottom: 2px solid transparent;
&:hover {
border-bottom: 2px solid black;
border-bottom: 2px solid var(--color-font-1);
}
}

View File

@@ -266,11 +266,9 @@ const {
<div class="panel-page-item">
<div class="list-header">
<div class="left">
<Tooltip title="切换词典">
<IconWrapper>
<Icon @click="emitter.emit(EventKey.openDictModal,'list')" icon="basil:exchange-outline"/>
</IconWrapper>
</Tooltip>
<BaseIcon title="切换词典"
@click="emitter.emit(EventKey.openDictModal,'list')"
icon="carbon:change-catalog"/>
<div class="title">
{{ store.dictTitle }}
</div>

View File

@@ -268,11 +268,9 @@ onUnmounted(() => {
>
<div class="list-header">
<div class="left">
<Tooltip title="切换词典">
<IconWrapper>
<Icon @click="emitter.emit(EventKey.openDictModal,'list')" icon="basil:exchange-outline"/>
</IconWrapper>
</Tooltip>
<BaseIcon title="切换词典"
@click="emitter.emit(EventKey.openDictModal,'list')"
icon="carbon:change-catalog"/>
<div class="title">
{{ store.dictTitle }}
</div>

View File

@@ -383,7 +383,7 @@ defineExpose({save, getEditArticle: () => cloneDeep(editArticle)})
}
.row {
flex: 1;
flex: 10;
width: 33%;
//height: 100%;
display: flex;
@@ -396,8 +396,8 @@ defineExpose({save, getEditArticle: () => cloneDeep(editArticle)})
flex-direction: column;
}
&:nth-child(2) {
opacity: 1;
&:nth-child(1) {
flex: 7;
}
.title {
@@ -436,7 +436,6 @@ defineExpose({save, getEditArticle: () => cloneDeep(editArticle)})
flex: 1;
overflow: auto;
border-radius: 8rem;
background: var(--color-main-bg);
.section {
background: var(--color-item-bg);

View File

@@ -0,0 +1,365 @@
<script setup lang="ts">
import {saveAs} from "file-saver";
import {onMounted, onUnmounted} from "vue";
import {Article, DefaultArticle, DictType, Sort} from "@/types.ts";
import BaseButton from "@/components/BaseButton.vue";
import {cloneDeep} from "lodash-es";
import BaseIcon from "@/components/BaseIcon.vue";
import {useBaseStore} from "@/stores/base.ts";
import {$computed, $ref} from "vue/macros";
import List from "@/components/list/List.vue";
import {v4 as uuidv4} from 'uuid';
import Dialog from "@/components/dialog/Dialog.vue";
import EditArticle from "@/components/article/EditArticle.vue";
import {emitter, EventKey} from "@/utils/eventBus.ts";
import {useDisableEventListener} from "@/hooks/event.ts";
import {MessageBox} from "@/utils/MessageBox.tsx";
import {useRuntimeStore} from "@/stores/runtime.ts";
const base = useBaseStore()
const runtimeStore = useRuntimeStore()
let article = $ref<Article>(cloneDeep(DefaultArticle))
let show = $ref(false)
let showImportBtn = $ref(true)
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.textCustomTranslate = editArticle.textCustomTranslate.trim()
editArticle.textNetworkTranslate = editArticle.textNetworkTranslate.trim()
if (
editArticle.title !== article.title ||
editArticle.titleTranslate !== article.titleTranslate ||
editArticle.text !== article.text ||
editArticle.textCustomTranslate !== article.textCustomTranslate ||
editArticle.textNetworkTranslate !== article.textNetworkTranslate ||
editArticle.useTranslateType !== article.useTranslateType
) {
return MessageBox.confirm(
'检测到数据有变动,是否保存?',
'提示',
async () => {
let r = await editArticleRef.save('save')
if (r) resolve(true)
},
() => void 0,
)
}
} else {
if (editArticle.title.trim() && editArticle.text.trim()) {
return MessageBox.confirm(
'检测到数据有变动,是否保存?',
'提示',
async () => {
let r = await editArticleRef.save('save')
if (r) resolve(true)
},
() => void 0,
)
}
}
resolve(true)
})
}
async function add() {
let r = await checkDataChange()
if (r) {
article = cloneDeep(DefaultArticle)
}
}
function importData(e: Event) {
showImportBtn = false
let file = e.target.files[0]
let reader = new FileReader();//新建一个FileReader
reader.readAsText(file, "UTF-8");//读取文件
reader.onload = function (evt) { //读取完文件之后会回来这里
let fileString = evt.target.result; // 读取文件内容
// console.log('fileString', fileString)
try {
let obj: any = JSON.parse(fileString)
console.log('obj', obj)
if (!obj?.name) {
showImportBtn = true
return ElMessage.error('请填写词典名称!')
} else {
if (base.myDictList.find(v => v.name === obj.name)) {
showImportBtn = true
return ElMessage.error('词典名称已存在!')
}
}
if (!obj?.articles) {
showImportBtn = true
return ElMessage.error('请填写文章!')
}
if (!obj?.articles instanceof Array) {
showImportBtn = true
return ElMessage.error('请填写文章!')
}
for (let i = 0; i < obj.articles.length; i++) {
let item = obj.articles[i]
if (!item?.title) {
showImportBtn = true
return ElMessage.error(`请填写第${i + 1}篇文章名称!`)
}
if (!item?.text) {
showImportBtn = true
return ElMessage.error(`请填写第${i + 1}篇文章正文!`)
}
if (item?.useTranslateType === 'custom') {
if (!item?.textCustomTranslate) {
showImportBtn = true
return ElMessage.error(`请填写第${i + 1}篇文章 翻译 正文!`)
}
}
if (!obj.articles[i]?.titleTranslate) obj.articles[i].titleTranslate = ''
if (!obj.articles[i]?.textFormat) obj.articles[i].textFormat = ''
if (!obj.articles[i]?.textCustomTranslate) obj.articles[i].textCustomTranslate = ''
if (!obj.articles[i]?.newWords) obj.articles[i].newWords = []
if (!obj.articles[i]?.textCustomTranslateIsFormat) obj.articles[i].textCustomTranslateIsFormat = false
if (!obj.articles[i]?.useTranslateType) obj.articles[i].useTranslateType = 'none'
if (!obj.articles[i]?.textAllWords) obj.articles[i].textAllWords = []
if (!obj.articles[i]?.sections) obj.articles[i].sections = []
obj.articles[i].id = uuidv4()
}
obj.sort = Sort.normal
obj.type = DictType.customArticle
obj.originWords = []
obj.words = []
obj.chapterWords = []
obj.chapterWordNumber = 0
obj.chapterIndex = 0
obj.chapterWordIndex = 0
obj.url = ''
if (!obj.statistics) obj.statistics = []
ElMessage.success({
message: '导入成功,已切换到',
duration: 5000
})
base.myDictList.push(obj)
runtimeStore.editDict = cloneDeep(runtimeStore.editDict)
showImportBtn = true
} catch (e) {
showImportBtn = true
ElMessage.error('文件解析失败,报错原因:' + e.message)
}
}
}
function exportData() {
let data = {
name: runtimeStore.editDict.name,
articles: cloneDeep(runtimeStore.editDict.articles).map(v => {
delete v.sections
delete v.id
return v
}),
url: location.origin + runtimeStore.editDict.url,
statistics: runtimeStore.editDict.statistics,
}
let blob = new Blob([JSON.stringify(data, null, 2)], {type: "text/plain;charset=utf-8"});
saveAs(blob, `${data.name}.json`);
}
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 = uuidv4()
runtimeStore.editDict.articles.push(val)
setTimeout(()=>{
listEl.scrollBottom()
})
}
article = cloneDeep(val)
//TODO 保存完成后滚动到对应位置
ElMessage.success('保存成功!')
return true
}
function saveAndNext(val: Article) {
if (saveArticle(val)) {
add()
}
}
</script>
<template>
<Dialog
v-model="show"
:full-screen="true"
:header="false"
>
<div class="add-article">
<div class="slide">
<header>
<div class="dict-name">{{ runtimeStore.editDict.name }}</div>
<BaseIcon title="选择其他词典/文章" icon="carbon:change-catalog"/>
</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" v-if="showImportBtn">
<BaseButton>导入</BaseButton>
<input type="file" accept="application/json" @change="importData">
</div>
<BaseButton @click="exportData">导出</BaseButton>
<BaseButton @click="add">新增</BaseButton>
</div>
</div>
<EditArticle
ref="editArticleRef"
type="batch"
@save="saveArticle"
@saveAndNext="saveAndNext"
:article="article"/>
</div>
</Dialog>
</template>
<style scoped lang="scss">
@import "@/assets/css/style.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-bg);
display: flex;
.close {
position: absolute;
right: 20rem;
top: 20rem;
}
.slide {
height: 100%;
background: var(--color-main-bg);
padding: 0 10rem;
display: flex;
flex-direction: column;
$height: 60rem;
header {
height: $height;
display: flex;
justify-content: space-between;
align-items: center;
//opacity: 0;
.dict-name {
font-size: 30rem;
color: var(--color-font-1);
}
}
.name {
font-size: 18rem;
}
.translate-name {
font-size: 16rem;
}
.add {
width: 260rem;
box-sizing: border-box;
border-radius: 8rem;
margin-bottom: 10rem;
padding: 10rem;
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: 10rem;
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

@@ -240,7 +240,6 @@ function saveAndNext(val: Article) {
<div class="slide">
<header>
<div class="dict-name">{{ runtimeStore.editDict.name }}</div>
<BaseIcon title="选择其他词典/文章" icon="carbon:change-catalog"/>
</header>
<List
ref="listEl"
@@ -299,8 +298,8 @@ function saveAndNext(val: Article) {
}
.slide {
width: 14vw;
height: 100%;
background: var(--color-main-bg);
padding: 0 10rem;
display: flex;
flex-direction: column;

View File

@@ -41,10 +41,11 @@ function showWordListModal(index: number, item: Word[]) {
<div class="flex gap10">
<input type="radio" :checked="activeIndex === index">
<template v-if="isArticle">
<div
@click.stop="emitter.emit(EventKey.openArticleListModal,item)"
>
<div class="title">{{ index + 1 }}.&nbsp;&nbsp;&nbsp;{{ item.title }}</div>
<div>
<div class="title"
@click.stop="emitter.emit(EventKey.openArticleListModal,item)"
>{{ index + 1 }}.&nbsp;&nbsp;&nbsp;{{ item.title }}
</div>
<div class="item-sub-title" v-if="item.titleTranslate"> {{ item.titleTranslate }}</div>
</div>
</template>

View File

@@ -166,14 +166,13 @@ defineExpose({scrollBottom})
.search {
margin: 10rem 0;
width: 260rem;
}
.list {
.item {
width: 260rem;
box-sizing: border-box;
background: #e1e1e1;
background: var(--color-item-bg);
color: var(--color-font-1);
border-radius: 8rem;
margin-bottom: 10rem;
padding: 10rem;
@@ -196,7 +195,7 @@ defineExpose({scrollBottom})
&.active {
background: var(--color-item-active);
color: white;
color: var(--color-font-1);
}
&.draggable {