This commit is contained in:
zyronon
2023-10-13 02:14:11 +08:00
parent 375420cf69
commit 85e0330d5f
13 changed files with 213 additions and 151 deletions

View File

@@ -37,4 +37,6 @@ BaseIcon 在选中模式下,应该显示白色
没有内容时,要显示占位符
A cold welcome 有bug
A cold welcome 有bug
[EditAbleText.vue](src%2Fcomponents%2FEditAbleText.vue) 不能自动聚焦

View File

@@ -345,11 +345,16 @@ defineExpose({save, getEditArticle: () => cloneDeep(editArticle)})
</div>
</div>
<div class="options" v-if="editArticle.text.trim()">
<div class="warning">
<template v-if="failCount && editArticle.useTranslateType !== TranslateType.none">
<div class="status">
<span>状态</span>
<div class="warning" v-if="failCount && editArticle.useTranslateType !== TranslateType.none">
<Icon icon="typcn:warning-outline"/>
共有{{ failCount }}句没有翻译
</template>
</div>
<div class="success" v-else>
<Icon icon="mdi:success-circle-outline"/>
翻译完成
</div>
</div>
<div class="left">
<BaseButton @click="save('save')">保存</BaseButton>
@@ -456,13 +461,24 @@ defineExpose({save, getEditArticle: () => cloneDeep(editArticle)})
align-items: center;
justify-content: space-between;
.status {
display: flex;
align-items: center;
}
.warning {
display: flex;
align-items: center;
font-size: 20rem;
color: red;
gap: 10rem;
}
.success {
display: flex;
align-items: center;
font-size: 20rem;
color: #67C23A;
}
.left {

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import {saveAs} from "file-saver";
import {onUnmounted, reactive} from "vue";
import {Article, DefaultArticle, DictType, Sort, TranslateType} from "@/types.ts";
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";
@@ -9,16 +9,17 @@ import {useBaseStore} from "@/stores/base.ts";
import {$computed, $ref} from "vue/macros";
import List from "@/components/List.vue";
import {v4 as uuidv4} from 'uuid';
import {Icon} from "@iconify/vue";
import Modal from "@/components/Modal/Modal.vue";
import EditArticle from "@/components/Article/EditArticle.vue";
import {onMounted} from "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)
@@ -40,59 +41,64 @@ onUnmounted(() => {
useDisableEventListener(() => show)
function selectArticle(item: Article) {
article = cloneDeep(item)
// console.log('article', article)
async function selectArticle(item: Article) {
let r = await checkDataChange()
if (r) {
article = cloneDeep(item)
}
}
function add() {
let editArticle: Article = editArticleRef.getEditArticle()
function checkDataChange() {
return new Promise(resolve => {
let editArticle: Article = editArticleRef.getEditArticle()
const newArticle = () => {
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)
// article.title = 'a'
// article.text = 'b'
}
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) newArticle()
},
() => void 0,
)
}
} else {
if (editArticle.title.trim() && editArticle.text.trim()) {
return MessageBox.confirm(
'检测到数据有变动,是否保存?',
'提示',
async () => {
let r = await editArticleRef.save('save')
if (r) newArticle()
},
() => void 0,
)
}
}
newArticle()
}
function importData(e: Event) {
@@ -166,7 +172,7 @@ function importData(e: Event) {
duration: 5000
})
base.myDicts.push(obj)
base.current.editIndex = base.myDicts.length - 1
runtimeStore.editDict = cloneDeep(runtimeStore.editDict)
showImportBtn = true
} catch (e) {
showImportBtn = true
@@ -177,49 +183,57 @@ function importData(e: Event) {
function exportData() {
let data = {
name: base.currentEditDict.name,
articles: cloneDeep(base.currentEditDict.articles).map(v => {
name: runtimeStore.editDict.name,
articles: cloneDeep(runtimeStore.editDict.articles).map(v => {
delete v.sections
delete v.id
return v
}),
url: location.origin + base.currentEditDict.url,
statistics: base.currentEditDict.statistics,
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) {
function saveArticle(val: Article): boolean {
console.log('saveArticle', val)
if (val.id !== '-1') {
let rIndex = base.currentEditDict.articles.findIndex(v => v.id === val.id)
if (val.id) {
let rIndex = runtimeStore.editDict.articles.findIndex(v => v.id === val.id)
if (rIndex > -1) {
base.currentEditDict.articles[rIndex] = cloneDeep(val)
runtimeStore.editDict.articles[rIndex] = cloneDeep(val)
}
} else {
let has = base.currentEditDict.articles.find((item: Article) => item.title === val.title)
let has = runtimeStore.editDict.articles.find((item: Article) => item.title === val.title)
if (has) {
return ElMessage.error('已存在同名文章!')
ElMessage.error('已存在同名文章!')
return false
}
val.id = uuidv4()
base.currentEditDict.articles.push(val)
article = cloneDeep(val)
runtimeStore.editDict.articles.push(val)
}
article = cloneDeep(val)
//TODO 保存完成后滚动到对应位置
ElMessage.success('保存成功!')
return true
}
function saveAndNext(val: Article) {
if (saveArticle(val)) {
add()
}
}
const list = $computed(() => {
if (article.id === '-1') {
return base.currentEditDict.articles.concat([article])
if (!article.id) {
return runtimeStore.editDict.articles.concat([article])
}
return base.currentEditDict.articles
return runtimeStore.editDict.articles
})
function getTitle(item: Article, index: number,) {
if (item.id === '-1') return 'New article'
if (!item.id) return 'New article'
return `${index + 1}. ${item.title}`
}
</script>
@@ -233,7 +247,7 @@ function getTitle(item: Article, index: number,) {
<div class="add-article">
<div class="slide">
<header>
<div class="dict-name">{{ base.currentEditDict.name }}</div>
<div class="dict-name">{{ runtimeStore.editDict.name }}</div>
<BaseIcon title="选择其他词典/文章" icon="carbon:change-catalog"/>
</header>
<List
@@ -260,12 +274,8 @@ function getTitle(item: Article, index: number,) {
ref="editArticleRef"
type="batch"
@save="saveArticle"
@saveAndNext="saveAndNext"
:article="article"/>
<Icon @click="show = false"
class="close hvr-grow pointer"
width="20" color="#929596"
icon="ion:close-outline"/>
</div>
</Modal>
</template>

View File

@@ -15,7 +15,7 @@ const props = withDefaults(defineProps<IProps>(), {
modelValue: false
})
const emit = defineEmits<{
save: [],
save: [val: Article]
}>()
</script>
@@ -29,7 +29,7 @@ const emit = defineEmits<{
<div class="wrapper">
<EditArticle
:article="article"
@save="emit('save')"
@save="val => emit('save',val)"
/>
</div>
</Modal>

View File

@@ -5,13 +5,11 @@ import {$ref} from "vue/macros";
import {watchEffect} from "vue";
interface IProps {
value?: string,
fontSize?: string,
value: string,
}
const props = withDefaults(defineProps<IProps>(), {
value: '',
fontSize: '16rem'
})
const emit = defineEmits([
@@ -29,24 +27,34 @@ function save() {
emit('save', editVal)
edit = false
}
function toggle() {
edit = !edit
}
</script>
<template>
<div class="edit-text" v-if="edit">
<div
v-if="edit"
class="edit-text">
<el-input
v-model="editVal"
ref="inputRef"
autosize
autofocus
type="textarea"
:input-style="`color:black;font-size: ${fontSize};`"
:input-style="`color:black;font-size: 16rem;`"
/>
<div class="options">
<BaseButton @click="edit = false">取消</BaseButton>
<BaseButton @click="toggle">取消</BaseButton>
<BaseButton @click="save">保存</BaseButton>
</div>
</div>
<div class="text"
:style="`font-size: ${fontSize};`"
v-else @click="edit = true">
<div
v-else
class="text"
:style="`font-size: 16rem;`"
@click="toggle">
{{ value }}
</div>
</template>

View File

@@ -124,15 +124,15 @@ async function cancel() {
]"
@click.stop="null"
>
<Tooltip title="关闭">
<Icon @click="close"
v-if="showClose"
class="close hvr-grow pointer"
width="20" color="#929596"
icon="ion:close-outline"/>
</Tooltip>
<div class="modal-header" v-if="header">
<div class="title">{{ props.title }}</div>
<Tooltip title="关闭">
<Icon @click="close"
v-if="showClose"
class="close hvr-grow pointer"
width="20" color="#929596"
icon="ion:close-outline"/>
</Tooltip>
</div>
<div class="modal-body" :class="{padding}">
<slot></slot>
@@ -252,6 +252,13 @@ $header-height: 60rem;
flex-direction: column;
transition: transform $time, opacity $time;
.close{
position: absolute;
right: 20rem;
top: 20rem;
z-index: 999;
}
.modal-header {
display: flex;
justify-content: space-between;

View File

@@ -83,7 +83,7 @@ onUnmounted(() => {
.footer {
width: var(--toolbar-width);
margin-bottom: 30rem;
margin-bottom: 10rem;
transition: all .3s;
position: relative;
margin-top: 30rem;
@@ -93,7 +93,7 @@ onUnmounted(() => {
margin-top: 65rem;
.progress {
bottom: calc(100% + 30rem);
bottom: calc(100% + 20rem);
}
}
.bottom {
@@ -102,7 +102,7 @@ onUnmounted(() => {
box-sizing: border-box;
border-radius: 10rem;
background: var(--color-header-bg);
padding: 2rem 10rem 10rem 10rem;
padding: 3rem 10rem 10rem 10rem;
z-index: 2;
.stat {

View File

@@ -16,7 +16,7 @@ import {Article, DefaultArticle, TranslateType} from "@/types.ts";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {renewSectionTexts, renewSectionTranslates} from "@/hooks/translate.ts";
import {MessageBox} from "@/utils/MessageBox.tsx";
import EditArticleModal from "@/components/Article/EditSingleArticleModal.vue";
import EditSingleArticleModal from "@/components/Article/EditSingleArticleModal.vue";
const practiceStore = usePracticeStore()
const store = useBaseStore()
@@ -55,11 +55,13 @@ watch(() => store.load, n => {
}
})
watch([() => store.current.index, () => store.current.dictType], n => {
watch([
() => store.current.index,
() => store.current.dictType,
() => store.currentDict.chapterIndex], n => {
getCurrentPractice()
})
function getCurrentPractice() {
// console.log('store.currentDict',store.currentDict)
if (store.isArticle) {
@@ -157,11 +159,11 @@ function next() {
repeat()
}
function saveArticle() {
console.log('saveArticle')
function saveArticle(val: Article) {
console.log('saveArticle', val)
showEditArticle = false
articleData.article = cloneDeep(store.currentDict.articles[store.currentDict.chapterIndex])
// store.currentDict.articles[store.currentDict.chapterIndex] = articleData.article = article
// articleData.article = cloneDeep(store.currentDict.articles[store.currentDict.chapterIndex])
store.currentDict.articles[store.currentDict.chapterIndex] = articleData.article = val
}
function test() {
@@ -200,9 +202,9 @@ function test() {
@repeat="repeat"
@next="next"
/>
<EditArticleModal v-model="showEditArticle"
:article="editArticle"
@save="saveArticle"
<EditSingleArticleModal v-model="showEditArticle"
:article="editArticle"
@save="saveArticle"
/>
</template>

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import {dictionaryResources} from '@/assets/dictionary.ts'
import {State, useBaseStore} from "@/stores/base.ts"
import {useBaseStore} from "@/stores/base.ts"
import {watch} from "vue"
import {Dict, DictionaryResource, DictType, Sort, Word} from "@/types.ts"
import {chunk, cloneDeep} from "lodash-es";
@@ -15,16 +15,16 @@ import jpFlag from '@/assets/img/flags/ja.png'
import bookFlag from '@/assets/img/flags/book.png'
import DictGroup from "@/components/Toolbar/DictGroup.vue";
import {v4 as uuidv4} from "uuid";
import VolumeIcon from "@/components/VolumeIcon.vue";
import {ActivityCalendar} from "vue-activity-calendar";
import "vue-activity-calendar/style.css";
import {usePlayWordAudio} from "@/hooks/sound.ts";
import ChapterList from "@/components/ChapterList.vue";
import WordListModal from "@/components/WordListModal.vue";
import {emitter, EventKey} from "@/utils/eventBus.ts";
import {isArticle} from "@/hooks/article.ts";
import {useRuntimeStore} from "@/stores/runtime.ts";
const store = useBaseStore()
const runtimeStore = useRuntimeStore()
interface IProps {
modelValue?: boolean,
@@ -48,21 +48,20 @@ const options = [
const base = useBaseStore()
let currentLanguage = $ref('en')
let currentSelectDict: Dict = $ref(cloneDeep(store.currentDict))
let step = $ref(1)
watch(() => props.modelValue, (n: boolean) => {
let rIndex = base.myDicts.findIndex((v: Dict) => v.name === store.currentDict.name)
if (rIndex > -1) {
base.current.editIndex = rIndex
let find = base.myDicts.find((v: Dict) => v.name === store.currentDict.name)
if (find) {
runtimeStore.editDict = cloneDeep(find)
}
})
async function selectDict(item: DictionaryResource) {
step = 1
let rIndex = base.myDicts.findIndex((v: Dict) => v.name === item.name)
if (rIndex > -1) {
base.current.editIndex = rIndex
let find = base.myDicts.find((v: Dict) => v.name === item.name)
if (find) {
runtimeStore.editDict = cloneDeep(find)
} else {
let data = {
sort: Sort.normal,
@@ -86,8 +85,7 @@ async function selectDict(item: DictionaryResource) {
v.id = uuidv4()
return v
}))
base.myDicts.push(data)
base.current.editIndex = base.myDicts.length - 1
runtimeStore.editDict = cloneDeep(data)
})
} else {
data.type = DictType.publicDict
@@ -96,15 +94,15 @@ async function selectDict(item: DictionaryResource) {
data.originWords = v
data.words = v
data.chapterWords = chunk(v, data.chapterWordNumber)
base.myDicts.push(data)
base.current.editIndex = base.myDicts.length - 1
runtimeStore.editDict = cloneDeep(data)
console.log(' runtimeStore.editDict', runtimeStore.editDict)
})
}
}
}
function changeDict() {
store.changeDict(base.currentEditDict)
store.changeDict(runtimeStore.editDict)
close()
}
@@ -113,7 +111,7 @@ function close() {
}
function resetChapterList() {
base.currentEditDict.chapterWords = chunk(base.currentEditDict.words, base.currentEditDict.chapterWordNumber)
runtimeStore.editDict.chapterWords = chunk(runtimeStore.editDict.words, runtimeStore.editDict.chapterWordNumber)
}
function groupBy<T>(elements: T[], iteratee: (value: T) => string) {
@@ -162,15 +160,17 @@ function showWord(list: Word[]) {
}
const dictIsArticle = $computed(() => {
return isArticle(base.currentEditDict.type)
return isArticle(runtimeStore.editDict.type)
})
</script>
<template>
<Modal :show-close="false"
:header="false"
@close="close">
<Modal
:header="false"
:model-value="props.modelValue"
:show-close="false"
@close="close">
<div class="slide">
<div class="slide-list" :class="`step${step}`">
<div class="dict-page">
@@ -193,7 +193,7 @@ const dictIsArticle = $computed(() => {
<div class="dict-list-wrapper">
<DictGroup
v-for="item in groupedByCategoryAndTag"
:select-dict-name="base.currentEditDict.name"
:select-dict-name="runtimeStore.editDict.name"
@selectDict="selectDict"
@detail="step = 1"
:groupByTag="item[1]"/>
@@ -216,16 +216,16 @@ const dictIsArticle = $computed(() => {
</header>
<div class="page-content">
<div class="detail">
<div class="name">{{ base.currentEditDict.name }}</div>
<div class="desc">{{ base.currentEditDict.description }}</div>
<div class="name">{{ runtimeStore.editDict.name }}</div>
<div class="desc">{{ runtimeStore.editDict.description }}</div>
<div class="num"
v-if="dictIsArticle"
>总文章{{ base.currentEditDict.articles.length }}
>总文章{{ runtimeStore.editDict.articles.length }}
</div>
<div class="num"
v-else
@click="emitter.emit(EventKey.openWordListModal,{title:'所有单词',list:base.currentEditDict.words})">
总词汇<span class="count">{{ base.currentEditDict.length }}</span>
@click="emitter.emit(EventKey.openWordListModal,{title:'所有单词',list:runtimeStore.editDict.words})">
总词汇<span class="count">{{ runtimeStore.editDict.length }}</span>
</div>
<div class="num">开始日期-</div>
<div class="num">花费时间-</div>
@@ -238,16 +238,16 @@ const dictIsArticle = $computed(() => {
</div>
<div class="setting">
<div class="common-title">学习设置</div>
<div class="row" v-if="!isArticle(base.currentEditDict.type)">
<div class="row" v-if="!isArticle(runtimeStore.editDict.type)">
<div class="label">每章单词数</div>
<el-slider :min="10"
:step="10"
:max="100"
v-model="base.currentEditDict.chapterWordNumber"
v-model="runtimeStore.editDict.chapterWordNumber"
@change="resetChapterList"
/>
<div class="option">
<span>{{ base.currentEditDict.chapterWordNumber }}</span>
<span>{{ runtimeStore.editDict.chapterWordNumber }}</span>
</div>
</div>
<div class="row">
@@ -311,19 +311,19 @@ const dictIsArticle = $computed(() => {
<div class="other">
<div class="common-title">
<template v-if="dictIsArticle">
文章列表{{ base.currentEditDict.articles.length }}
文章列表{{ runtimeStore.editDict.articles.length }}
</template>
<template v-else>
章节列表{{
base.currentEditDict.chapterWords.length
}}(每章{{ base.currentEditDict.chapterWordNumber }})
runtimeStore.editDict.chapterWords.length
}}(每章{{ runtimeStore.editDict.chapterWordNumber }})
</template>
</div>
<ChapterList
@showWord="showWord"
:is-article="dictIsArticle"
v-model:active-index="base.currentEditDict.chapterIndex"
:dict="base.currentEditDict"/>
v-model:active-index="runtimeStore.editDict.chapterIndex"
:dict="runtimeStore.editDict"/>
</div>
</div>
<div v-if="false" class="activity">
@@ -340,9 +340,9 @@ const dictIsArticle = $computed(() => {
/>
</div>
<div class="footer">
<BaseButton @click="step = 0">导出</BaseButton>
<BaseButton @click="step = 0">返回</BaseButton>
<BaseButton @click="changeDict">确定</BaseButton>
<!-- <BaseButton @click="step = 0">导出</BaseButton>-->
<BaseButton @click="close">关闭</BaseButton>
<BaseButton @click="changeDict">切换</BaseButton>
</div>
</div>
</div>

View File

@@ -102,7 +102,7 @@ watch(() => settingStore.showToolbar, n => {
color="#999"/>
</Tooltip>
</header>
<DictModal v-if="showDictModal" @close="showDictModal = false"/>
<DictModal :model-value="showDictModal" @close="showDictModal = false"/>
<SettingModal v-if="showSettingModal" @close="showSettingModal = false"/>
<FeedbackModal v-if="showFeedbackModal" @close="showFeedbackModal = false"/>
</template>

View File

@@ -262,6 +262,7 @@ export const useBaseStore = defineStore('base', {
} else {
let rIndex = this.myDicts.findIndex((v: Dict) => v.name === dict.name)
if (rIndex > -1) {
this.myDicts[rIndex] = dict
this.current.index = rIndex
} else {
this.myDicts.push(cloneDeep(dict))

View File

@@ -1,15 +1,31 @@
import {defineStore} from "pinia"
import {Dict, DictType, Sort} from "@/types.ts";
export interface RuntimeState {
disableEventListener: boolean,
modalList: Array<{ id: string | number, close: Function }>
editDict: Dict
}
export const useRuntimeStore = defineStore('runtime', {
state: (): RuntimeState => {
return {
disableEventListener: false,
modalList: []
modalList: [],
editDict: {
name: '',
sort: Sort.normal,
type: DictType.publicArticle,
originWords: [],
articles: [],
words: [],
chapterWordNumber: 15,
chapterWords: [],
chapterIndex: 0,
chapterWordIndex: 0,
statistics: [],
url: '',
},
}
},
})

View File

@@ -103,7 +103,7 @@ export interface Article {
export const DefaultArticle: Article = {
// id: uuidv4(),
id: '-1',
id: '',
title: '',
titleTranslate: '',
text: '',