Develop dictionary management function

This commit is contained in:
zyronon
2023-11-28 18:23:11 +08:00
parent 52b12771a2
commit 3e6bbfd282
8 changed files with 916 additions and 543 deletions

View File

@@ -71,6 +71,16 @@ watch(() => props.list, () => {
// listRef.scrollTo(0, 0)
})
function scrollToBottom() {
listRef.scrollToBottom()
}
function scrollToItem(index: number) {
listRef.scrollToItem(index)
}
defineExpose({scrollToBottom, scrollToItem})
</script>
<template>

View File

@@ -137,4 +137,19 @@ export async function checkDictHasTranslate(dict: Dict) {
}
}
}
//同步到我的词典列表
export function syncMyDictList(dict: Dict) {
const store = useBaseStore()
//任意修改,都将其变为自定义词典
dict.isCustom = true
dict.length = dict.words.length + dict.residueWords.length
let rIndex = store.myDictList.findIndex(v => v.id === dict.id)
if (rIndex > -1) {
store.myDictList[rIndex] = cloneDeep(dict)
} else {
store.myDictList.push(cloneDeep(dict))
}
}

View File

@@ -1,98 +1,44 @@
<script setup lang="ts">
import {dictionaryResources} from '@/assets/dictionary.ts'
import {useBaseStore} from "@/stores/base.ts"
import {onMounted, reactive, watch} from "vue"
import {DefaultDict, Dict, DictResource, DictType, Sort} from "@/types.ts"
import {nextTick, onMounted, watch} from "vue"
import {Dict, DictResource, DictType, Sort} from "@/types.ts"
import {cloneDeep, reverse, shuffle} from "lodash-es";
import {$computed, $ref} from "vue/macros";
import {Icon} from '@iconify/vue';
import {v4 as uuidv4} from "uuid";
import {$ref} from "vue/macros";
import "vue-activity-calendar/style.css";
import {isArticle} from "@/hooks/article.ts";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {useSettingStore} from "@/stores/setting.ts";
import {emitter, EventKey} from "@/utils/eventBus.ts";
import Slide from "@/components/Slide.vue";
import {FormInstance, FormRules} from "element-plus";
import BaseIcon from "@/components/BaseIcon.vue";
import BaseButton from "@/components/BaseButton.vue";
import {nanoid} from "nanoid";
import {no} from "@/utils";
import ArticleDictDetail from "@/pages/dict/components/ArticleDictDetail.vue";
import WordDictDetail from "@/pages/dict/components/WordDictDetail.vue";
import DictListPanel from "@/pages/dict/components/DictListPanel.vue";
import EditDict from "@/pages/dict/components/EditDict.vue";
const store = useBaseStore()
const settingStore = useSettingStore()
const runtimeStore = useRuntimeStore()
let detailRef = $ref()
let dictIsArticle = $ref(false)
let step = $ref(1)
let isAddDict = $ref(false)
async function selectDict(val: {
dict: DictResource | Dict,
index: number
}) {
let item = val.dict
console.log('item', item)
step = 1
isAddDict = false
// wordFormData.type = FormMode.None
// loading = true
let find: Dict = store.myDictList.find((v: Dict) => v.id === item.id)
if (find) {
runtimeStore.editDict = cloneDeep(find)
} else {
runtimeStore.editDict = cloneDeep({
...cloneDeep(DefaultDict),
...item,
})
//设置默认章节单词数
runtimeStore.editDict.chapterWordNumber = settingStore.chapterWordNumber
}
if ([DictType.collect, DictType.simple, DictType.wrong].includes(runtimeStore.editDict.type)) {
} else {
//如果不是自定义词典并且有url地址才去下载
if (!runtimeStore.editDict.isCustom && runtimeStore.editDict.url) {
let url = `./dicts/${runtimeStore.editDict.language}/${runtimeStore.editDict.type}/${runtimeStore.editDict.translateLanguage}/${runtimeStore.editDict.url}`;
if (runtimeStore.editDict.type === DictType.word) {
if (!runtimeStore.editDict.originWords.length) {
let r = await fetch(url)
let v = await r.json()
v.map(s => {
s.id = nanoid(6)
})
runtimeStore.editDict.originWords = cloneDeep(v)
changeSort(runtimeStore.editDict.sort)
}
}
if (runtimeStore.editDict.type === DictType.article) {
if (!runtimeStore.editDict.articles.length) {
let r = await fetch(url)
let v = await r.json()
runtimeStore.editDict.articles = cloneDeep(v.map(s => {
s.id = uuidv4()
return s
}))
}
}
}
}
// chapterList2 = Array.from({length: runtimeStore.editDict.chapterWords.length}).map((v, i) => ({id: i}))
// loading = false
dictIsArticle = val.dict.type === DictType.article
nextTick(() => {
detailRef?.getDictDetail(val)
})
}
function changeDict() {
store.changeDict(runtimeStore.editDict)
}
const dictIsArticle = $computed(() => {
return isArticle(runtimeStore.editDict.type)
})
function changeSort(v: Sort) {
if (v === Sort.normal) {
runtimeStore.editDict.words = cloneDeep(runtimeStore.editDict.originWords)
@@ -104,93 +50,14 @@ function changeSort(v: Sort) {
// resetChapterList()
}
/**/
/*词典相关*/
/**/
let isAddDict = $ref(false)
let categoryList = {}
let tagList = {}
const DefaultDictForm = {
id: '',
name: '',
description: '',
category: '',
tags: [],
translateLanguage: 'zh-CN',
language: 'en',
type: DictType.customWord
}
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'},
],
category: [{required: true, message: '请选择', trigger: 'change'}],
tags: [{required: true, message: '请选择', trigger: 'change'}],
})
watch(() => dictForm.language, () => isAddDict && (dictForm.category = ''))
watch(() => dictForm.category, () => isAddDict && (dictForm.tags = []))
function editDict() {
// dictForm.id = store.editDict.id
// dictForm.name = store.editDict.name
// dictForm.description = store.editDict.description
console.log('store.editDict', runtimeStore.editDict)
dictForm = cloneDeep(runtimeStore.editDict)
//直接复制上面会watch到category然后把tags 设置为空
setTimeout(() => isAddDict = true, 0)
}
function closeDictForm() {
if (dictForm.id) {
isAddDict = false
dictForm = cloneDeep(DefaultDictForm)
} else {
step = 0
}
}
async function onSubmit() {
await dictFormRef.validate((valid, fields) => {
if (valid) {
let data: Dict = {
...DefaultDict,
...dictForm,
}
//任意修改,都将其变为自定义词典
if (data.type === DictType.word) data.type = DictType.customWord
if (data.type === DictType.article) data.type = DictType.customArticle
if (data.id) {
let rIndex = store.myDictList.findIndex(v => v.id === data.id)
store.myDictList[rIndex] = cloneDeep(data)
runtimeStore.editDict = cloneDeep(data)
isAddDict = false
ElMessage.success('修改成功')
} else {
data.id = 'custom-dict-' + Date.now()
if (store.myDictList.find(v => v.name === dictForm.name)) {
return ElMessage.warning('已有相同名称词典!')
} else {
store.myDictList.push(cloneDeep(data))
runtimeStore.editDict = cloneDeep(data)
isAddDict = false
ElMessage.success('添加成功')
}
}
console.log('submit!', data)
} else {
ElMessage.warning('请填写完整')
}
})
}
/**/
/*词典相关*/
@@ -199,7 +66,7 @@ async function onSubmit() {
watch(() => step, v => {
if (v === 0) {
// closeWordForm()
closeDictForm()
// closeDictForm()
// chapterWordNumber = settingStore.chapterWordNumber
}
})
@@ -211,20 +78,6 @@ watch(() => store.load, v => {
})
onMounted(() => {
dictionaryResources.map(v => {
if (categoryList[v.language]) {
if (!categoryList[v.language].find(w => w === v.category)) {
categoryList[v.language].push(v.category)
}
} else {
categoryList[v.language] = [v.category]
}
if (tagList[v.category]) {
tagList[v.category] = Array.from(new Set(tagList[v.category].concat(v.tags)))
} else {
tagList[v.category] = v.tags
}
})
selectDict({dict: store.currentDict, index: 0})
@@ -248,24 +101,22 @@ onMounted(() => {
selectDict({dict: store.simple, index: 0})
// addWord('residue')
}
})
// console.log('categoryList', categoryList)
// console.log('tagList', tagList)
})
const isPinDict = $computed(() => {
return [DictType.collect, DictType.wrong, DictType.simple].includes(runtimeStore.editDict.type)
})
function resetDict() {
function addDict() {
step = 1
isAddDict = true
}
function importData() {
function back() {
step = 0
// isAddDict = false
}
function exportData() {
}
</script>
@@ -273,97 +124,25 @@ function exportData() {
<div id="DictDialog">
<Slide :slide-count="2" :step="step">
<DictListPanel
@add="step = 1"
@add="addDict"
@select-dict="selectDict"
/>
<div class="dict-detail-page">
<header>
<Icon icon="octicon:arrow-left-24"
@click.stop="step = 0"
width="20"/>
<div class="left">
<div class="top">
<div class="title">
{{ runtimeStore.editDict.name }}
</div>
<template v-if="!isPinDict">
<BaseIcon icon="tabler:edit" @click='editDict'/>
<BaseIcon icon="ph:star" @click='no'/>
<BaseButton size="small" v-if="runtimeStore.editDict.isCustom" @click="resetDict">恢复默认</BaseButton>
</template>
<div class="import hvr-grow">
<BaseButton size="small">导入</BaseButton>
<input type="file"
accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
@change="importData">
</div>
<BaseButton size="small" @click="exportData">导出</BaseButton>
</div>
<div class="desc" v-if="runtimeStore.editDict.description">{{ runtimeStore.editDict.description }}</div>
<div class="num" v-if="dictIsArticle">文章: {{ runtimeStore.editDict.articles.length }}</div>
<div class="num" v-else>总词汇: {{ runtimeStore.editDict.originWords.length }}</div>
</div>
</header>
<div class="detail" v-if="!isAddDict">
<ArticleDictDetail v-if="dictIsArticle"/>
<WordDictDetail v-else/>
</div>
<div class="edit-dict" v-else>
<div class="wrapper">
<div class="common-title">{{ dictForm.id ? '修改' : '添加' }}词典</div>
<el-form
ref="dictFormRef"
:rules="dictRules"
:model="dictForm"
label-width="120rem">
<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>
<el-form-item label="分类" prop="category">
<el-select v-model="dictForm.category" placeholder="请选择选项">
<el-option :label="i" :value="i" v-for="i in categoryList[dictForm.language]"/>
</el-select>
</el-form-item>
<el-form-item label="标签" prop="tags">
<el-select
multiple
v-model="dictForm.tags" placeholder="请选择选项">
<el-option :label="i" :value="i" v-for="i in tagList[dictForm.category]"/>
</el-select>
</el-form-item>
<el-form-item label="类型">
<el-select v-model="dictForm.type" placeholder="请选择选项">
<el-option label="单词" :value="DictType.customWord"/>
<el-option label="文章" :value="DictType.customArticle"/>
</el-select>
</el-form-item>
<div class="flex-center">
<el-button @click="closeDictForm">关闭</el-button>
<el-button type="primary" @click="onSubmit">确定</el-button>
</div>
</el-form>
</div>
</div>
<EditDict
v-if="isAddDict"
:isAdd="true"
@cancel="isAddDict = false;step = 0"
@submit="selectDict({dict:runtimeStore.editDict})"/>
<template v-else>
<ArticleDictDetail
ref="detailRef"
@back="back"
v-if="dictIsArticle"/>
<WordDictDetail
ref="detailRef"
@back="back"
v-else/>
</template>
</div>
</Slide>
</div>
@@ -391,74 +170,6 @@ $header-height: 60rem;
.dict-detail-page {
width: 50%;
height: 100%;
$header-height: 60rem;
box-sizing: border-box;
display: flex;
flex-direction: column;
header {
width: 100%;
display: flex;
box-sizing: border-box;
align-items: center;
color: var(--color-font-1);
padding: 0 var(--space);
gap: 20rem;
margin-bottom: 20rem;
svg {
cursor: pointer
}
.left {
display: flex;
gap: 10rem;
flex-direction: column;
color: var(--color-font-2);
.top {
color: var(--color-font-1);
display: flex;
gap: 10rem;
font-size: 20rem;
align-items: center;
}
.import {
display: inline-flex;
position: relative;
input {
position: absolute;
height: 100%;
width: 100%;
opacity: 0;
}
}
}
}
.detail {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.edit-dict {
flex: 1;
width: 100%;
display: flex;
justify-content: center;
.wrapper {
width: 500rem;
}
.el-select {
width: 100%;
}
}
}
</style>

View File

@@ -5,16 +5,33 @@ import BaseButton from "@/components/BaseButton.vue";
import Empty from "@/components/Empty.vue";
import {useRuntimeStore} from "@/stores/runtime.ts";
import ArticleList3 from "@/components/list/ArticleList3.vue";
import {$ref} from "vue/macros";
import VirtualWordList2 from "@/components/list/VirtualWordList2.vue";
import {$computed, $ref} from "vue/macros";
import {cloneDeep} from "lodash-es";
import {Article, DefaultArticle, TranslateType} from "@/types.ts";
import {Article, DefaultArticle, DefaultDict, Dict, DictResource, DictType, Sort, TranslateType} from "@/types.ts";
import {emitter, EventKey} from "@/utils/eventBus.ts";
import EditBatchArticleModal from "@/components/article/EditBatchArticleModal.vue";
import {no} from "@/utils";
import {Icon} from "@iconify/vue";
import EditDict from "@/pages/dict/components/EditDict.vue";
import {nanoid} from "nanoid";
import {useBaseStore} from "@/stores/base.ts";
import {useSettingStore} from "@/stores/setting.ts";
import MiniDialog from "@/components/dialog/MiniDialog.vue";
import * as XLSX from "xlsx";
import {MessageBox} from "@/utils/MessageBox.tsx";
const store = useBaseStore()
const settingStore = useSettingStore()
const runtimeStore = useRuntimeStore()
let chapterIndex = $ref(-1)
let article: Article = $ref(cloneDeep(DefaultArticle))
let isEditDict = $ref(false)
let showExport = $ref(false)
let loading = $ref(false)
let listRef = $ref()
const isPinDict = $computed(() => {
return [DictType.collect, DictType.wrong, DictType.simple].includes(runtimeStore.editDict.type)
})
function handleCheckedChange(val) {
chapterIndex = val.index
@@ -35,66 +52,281 @@ function delArticle(index: number) {
ElMessage.success('删除成功!')
}
const emit = defineEmits<{
back: []
}>()
async function getDictDetail(val: { dict: DictResource | Dict, index: number }) {
let item = val.dict
// console.log('word-getDictDetail', item)
chapterIndex = -1
loading = true
let find: Dict = store.myDictList.find((v: Dict) => v.id === item.id)
if (find) {
runtimeStore.editDict = cloneDeep(find)
} else {
runtimeStore.editDict = cloneDeep({
...cloneDeep(DefaultDict),
...item,
})
runtimeStore.editDict.id = nanoid(6)
}
//如果不是自定义词典并且有url地址才去下载
if (!runtimeStore.editDict.isCustom && runtimeStore.editDict.url) {
let url = `./dicts/${runtimeStore.editDict.language}/${runtimeStore.editDict.type}/${runtimeStore.editDict.translateLanguage}/${runtimeStore.editDict.url}`;
if (!runtimeStore.editDict.articles.length) {
let r = await fetch(url)
let v = await r.json()
v.map(s => {
s.id = nanoid(6)
})
runtimeStore.editDict.articles = cloneDeep(v)
}
}
runtimeStore.editDict.length = runtimeStore.editDict.articles.length + runtimeStore.editDict
loading = false
}
async function resetDict() {
MessageBox.confirm(
'删除所有自定义内容: 章节、排序、单词,并恢复至默认状态,确认恢复?',
'提示',
async () => {
closeWordForm()
chapterIndex = -1
if (runtimeStore.editDict.url) {
runtimeStore.editDict.sort = Sort.normal
runtimeStore.editDict.isCustom = false
runtimeStore.editDict.chapterWordNumber = settingStore.chapterWordNumber
let url = `./dicts/${runtimeStore.editDict.language}/${runtimeStore.editDict.type}/${runtimeStore.editDict.translateLanguage}/${runtimeStore.editDict.url}`;
let r = await fetch(url)
let v = await r.json()
v.map(s => {
s.id = nanoid(6)
})
runtimeStore.editDict.originWords = cloneDeep(v)
changeSort(runtimeStore.editDict.sort)
ElMessage.success('恢复成功')
} else {
ElMessage.success('恢复失败')
}
}
)
}
function importData(e: any) {
let file = e.target.files[0]
if (!file) return
// no()
let reader = new FileReader();
reader.onload = function (e) {
let data = e.target.result;
// 读取二进制的excel
let workbook = XLSX.read(data, {type: 'binary'});
let res: any[] = XLSX.utils.sheet_to_json(workbook.Sheets['Sheet1']);
if (res.length) {
let articles = res.map(v => {
if (v['原文标题'] && v['原文正文']) {
let article: Article = {
...DefaultArticle,
textCustomTranslateIsFormat: false,
useTranslateType: TranslateType.custom,
id: nanoid(6),
checked: false,
title: String(v['原文标题']),
text: String(v['原文正文']),
titleTranslate: String(v['译文标题']),
textCustomTranslate: String(v['译文正文']),
}
return article
}
}).filter(v => v)
let repeat = []
let noRepeat = []
articles.map((v: any) => {
let rIndex = runtimeStore.editDict.articles.findIndex(s => s.title === v.title)
if (rIndex > -1) {
v.index = rIndex
repeat.push(v)
} else {
noRepeat.push(v)
}
})
runtimeStore.editDict.articles = runtimeStore.editDict.articles.concat(noRepeat)
if (repeat.length) {
MessageBox.confirm(
'文章"' + repeat.map(v => v.title).join(', ') + '" 已存在,继续将会覆盖原有文章,是否继续?',
'检测到重复文章',
() => {
repeat.map(v => {
runtimeStore.editDict.articles[v.index] = v
delete runtimeStore.editDict.articles[v.index].index
})
setTimeout(listRef?.scrollToBottom, 100)
},
null,
() => ElMessage.success('导入成功!')
)
} else {
ElMessage.success('导入成功!')
}
} else {
ElMessage.warning('导入失败!原因:没有数据')
}
};
reader.readAsBinaryString(file);
}
function exportData(type: string) {
let list = []
let filename = ''
if (type === 'chapter') {
if (chapterIndex === -1) {
return ElMessage.error('请选择文章')
}
list = [article]
filename = runtimeStore.editDict.name + `-第${chapterIndex + 1}`
} else {
list = runtimeStore.editDict.articles
filename = runtimeStore.editDict.name
}
let wb = XLSX.utils.book_new()
let sheetData = list.map(v => {
return {
原文标题: v.title,
原文正文: v.text,
译文标题: v.titleTranslate,
译文正文: v.textCustomTranslate,
}
})
wb.Sheets['Sheet1'] = XLSX.utils.json_to_sheet(sheetData)
wb.SheetNames = ['Sheet1']
XLSX.writeFile(wb, `${filename}-zh-CN.xlsx`);
ElMessage.success(filename + ' 导出成功!')
showExport = false
}
function editDict() {
}
defineExpose({getDictDetail})
</script>
<template>
<div class="article-detail">
<div class="chapter-list">
<div class="header">
<div class="common-title">
<span>文章管理</span>
<div class="options">
<BaseIcon
@click="emitter.emit(EventKey.openArticleListModal)"
icon="fluent:add-20-filled"
title="新增章节"/>
<span>{{ runtimeStore.editDict.articles.length }}</span>
<header>
<div class="back" @click.stop="emit('back')">
<Icon icon="octicon:arrow-left-24" width="20"/>
</div>
<div class="left">
<div class="top">
<div class="title">
{{ runtimeStore.editDict.name }}
</div>
<template v-if="!isPinDict">
<BaseIcon icon="tabler:edit" @click='editDict'/>
<BaseIcon icon="ph:star" @click='no'/>
<BaseButton size="small" v-if="runtimeStore.editDict.isCustom" @click="resetDict">恢复默认</BaseButton>
</template>
<div class="import hvr-grow">
<BaseButton size="small">导入</BaseButton>
<input type="file"
accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
@change="importData">
</div>
<div class="export"
style="position: relative"
@click.stop="null">
<BaseButton size="small" @click="showExport = true">导出</BaseButton>
<MiniDialog
v-model="showExport"
style="width: 80rem;"
>
<div class="mini-row-title">
导出选项
</div>
<div class="mini-row">
<BaseButton size="small" @click="exportData('all')">全部文章</BaseButton>
</div>
<div class="mini-row">
<BaseButton size="small" @click="exportData('chapter')">当前章节</BaseButton>
</div>
</MiniDialog>
</div>
</div>
<div class="desc" v-if="runtimeStore.editDict.description">{{ runtimeStore.editDict.description }}</div>
<div class="num">文章: {{ runtimeStore.editDict.articles.length }}</div>
</div>
<div class="wrapper">
<ArticleList3
v-if="runtimeStore.editDict.articles.length"
:list="runtimeStore.editDict.articles"
@click="handleCheckedChange"
:active-index="chapterIndex"
>
<template v-slot:prefix="{data,index}">
<input type="radio" :checked="chapterIndex === index">
</template>
<template v-slot="{data,index}">
<BaseIcon
class-name="del"
@click="emitter.emit(EventKey.openArticleListModal,data)"
title="编辑"
icon="tabler:edit"/>
<BaseIcon
class-name="del"
@click="delArticle(index)"
title="删除"
icon="solar:trash-bin-minimalistic-linear"/>
</template>
</ArticleList3>
</header>
<EditDict
:isAdd="false"
v-if="isEditDict"
@cancel="isEditDict = false"
@submit="isEditDict = false"/>
<div v-else class="content">
<div class="chapter-list">
<div class="header">
<div class="common-title">
<span>文章管理</span>
<div class="options">
<BaseIcon
@click="emitter.emit(EventKey.openArticleListModal)"
icon="fluent:add-20-filled"
title="新增章节"/>
<span>{{ runtimeStore.editDict.articles.length }}</span>
</div>
</div>
</div>
<div class="wrapper">
<ArticleList3
ref="listRef"
v-if="runtimeStore.editDict.articles.length"
:list="runtimeStore.editDict.articles"
@click="handleCheckedChange"
:active-index="chapterIndex"
>
<template v-slot:prefix="{data,index}">
<input type="radio" :checked="chapterIndex === index">
</template>
<template v-slot="{data,index}">
<BaseIcon
class-name="del"
@click="emitter.emit(EventKey.openArticleListModal,data)"
title="编辑"
icon="tabler:edit"/>
<BaseIcon
class-name="del"
@click="delArticle(index)"
title="删除"
icon="solar:trash-bin-minimalistic-linear"/>
</template>
</ArticleList3>
<Empty v-else/>
</div>
</div>
<div class="article-content word-font-family">
<div class="title">
<div>{{ article.title }}</div>
</div>
<div class="text" v-if="article.text">
<div class="sentence" v-for="t in article.text.split('\n')">{{ t }}</div>
</div>
<Empty v-else/>
</div>
</div>
<div class="article-content word-font-family">
<div class="title">
<div>{{ article.title }}</div>
<div class="article-content">
<div class="title">
<div>{{ article.titleTranslate }}</div>
</div>
<div class="text" v-if="article.textCustomTranslate">
{{ article.textCustomTranslate }}
</div>
<Empty v-else/>
</div>
<div class="text" v-if="article.text">
<div class="sentence" v-for="t in article.text.split('\n')">{{ t }}</div>
</div>
<Empty v-else/>
</div>
<div class="article-content">
<div class="title">
<div>{{ article.titleTranslate }}</div>
</div>
<div class="text" v-if="article.textCustomTranslate">
{{ article.textCustomTranslate }}
</div>
<Empty v-else/>
</div>
</div>
<EditBatchArticleModal/>
@@ -105,7 +337,53 @@ function delArticle(index: number) {
width: 100%;
height: 100%;
display: flex;
gap: var(--space);
flex-direction: column;
overflow: hidden;
header {
width: 100%;
display: flex;
box-sizing: border-box;
align-items: center;
color: var(--color-font-1);
padding: 0 var(--space);
gap: 20rem;
margin-bottom: 20rem;
.back {
height: 100%;
display: flex;
align-items: center;
cursor: pointer;
}
.left {
display: flex;
gap: 10rem;
flex-direction: column;
color: var(--color-font-2);
.top {
color: var(--color-font-1);
display: flex;
gap: 10rem;
font-size: 20rem;
align-items: center;
}
.import {
display: inline-flex;
position: relative;
input {
position: absolute;
height: 100%;
width: 100%;
opacity: 0;
}
}
}
}
.box {
background: white;
@@ -117,6 +395,13 @@ function delArticle(index: number) {
flex-direction: column;
}
.content {
flex: 1;
display: flex;
gap: var(--space);
overflow: hidden;
}
.chapter-list {
width: 400rem;
height: 100%;

View File

@@ -0,0 +1,190 @@
<script setup lang="ts">
import {DefaultDict, Dict, DictType} from "@/types.ts";
import {cloneDeep} from "lodash-es";
import {$ref} from "vue/macros";
import {FormInstance, FormRules} from "element-plus";
import {onMounted, reactive, watch} from "vue";
import {dictionaryResources} from "@/assets/dictionary.ts";
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: []
cancel: []
}>()
const runtimeStore = useRuntimeStore()
const store = useBaseStore()
let categoryList = {}
let tagList = {}
let init = false
const DefaultDictForm = {
id: '',
name: '',
description: '',
category: '',
tags: [],
translateLanguage: 'zh-CN',
language: 'en',
type: DictType.customWord
}
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'},
],
category: [{required: true, message: '请选择', trigger: 'change'}],
tags: [{required: true, message: '请选择', trigger: 'change'}],
})
watch(() => dictForm.language, () => init && (dictForm.category = ''))
watch(() => dictForm.category, () => init && (dictForm.tags = []))
function closeDictForm() {
emit('cancel')
}
async function onSubmit() {
await dictFormRef.validate((valid, fields) => {
if (valid) {
let data: Dict = cloneDeep({
...DefaultDict,
...dictForm,
})
//任意修改,都将其变为自定义词典
data.isCustom = true
if (props.isAdd) {
data.id = 'custom-dict-' + Date.now()
if (store.myDictList.find(v => v.name === data.name)) {
return ElMessage.warning('已有相同名称词典!')
} else {
store.myDictList.push(data)
runtimeStore.editDict = data
ElMessage.success('添加成功')
}
} else {
syncMyDictList(data)
runtimeStore.editDict = data
ElMessage.success('修改成功')
}
emit('submit')
console.log('submit!', data)
} else {
ElMessage.warning('请填写完整')
}
})
}
onMounted(() => {
dictionaryResources.map(v => {
if (categoryList[v.language]) {
if (!categoryList[v.language].find(w => w === v.category)) {
categoryList[v.language].push(v.category)
}
} else {
categoryList[v.language] = [v.category]
}
if (tagList[v.category]) {
tagList[v.category] = Array.from(new Set(tagList[v.category].concat(v.tags)))
} else {
tagList[v.category] = v.tags
}
})
if (props.isAdd) {
dictForm = cloneDeep(DefaultDict)
} else {
dictForm = cloneDeep(runtimeStore.editDict)
}
//上面复制后watch会检测到变更从而把其他值变成空。这里加一个限制
setTimeout(() => {
init = true
})
})
</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="120rem">
<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>
<el-form-item label="分类" prop="category">
<el-select v-model="dictForm.category" placeholder="请选择选项">
<el-option :label="i" :value="i" v-for="i in categoryList[dictForm.language]"/>
</el-select>
</el-form-item>
<el-form-item label="标签" prop="tags">
<el-select
multiple
v-model="dictForm.tags" placeholder="请选择选项">
<el-option :label="i" :value="i" v-for="i in tagList[dictForm.category]"/>
</el-select>
</el-form-item>
<el-form-item label="类型">
<el-select v-model="dictForm.type" placeholder="请选择选项">
<el-option label="单词" :value="DictType.customWord"/>
<el-option label="文章" :value="DictType.customArticle"/>
</el-select>
</el-form-item>
<div class="flex-center">
<el-button @click="closeDictForm">关闭</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: 500rem;
}
.el-select {
width: 100%;
}
}
</style>

View File

@@ -6,10 +6,10 @@ import ChapterWordList from "@/pages/dict/components/ChapterWordList.vue";
import BaseButton from "@/components/BaseButton.vue";
import {$computed, $ref} from "vue/macros";
import {assign, chunk, cloneDeep, reverse, shuffle} from "lodash-es";
import {Sort, Word} from "@/types.ts";
import {DefaultDict, Dict, DictResource, DictType, Sort, Word} from "@/types.ts";
import {nanoid} from "nanoid";
import {FormInstance, FormRules} from "element-plus";
import {reactive} from "vue";
import {reactive, watch} from "vue";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {useBaseStore} from "@/stores/base.ts";
import {useSettingStore} from "@/stores/setting.ts";
@@ -17,6 +17,16 @@ import Dialog from "@/components/dialog/Dialog.vue";
import {MessageBox} from "@/utils/MessageBox.tsx";
import * as XLSX from "xlsx";
import WordListDialog from "@/components/dialog/WordListDialog.vue";
import {no} from "@/utils";
import {Icon} from "@iconify/vue";
import EditDict from "@/pages/dict/components/EditDict.vue";
import {syncMyDictList} from "@/hooks/dict.ts";
import MiniDialog from "@/components/dialog/MiniDialog.vue";
import {useWindowClick} from "@/hooks/event.ts";
const emit = defineEmits<{
back: []
}>()
const store = useBaseStore()
const settingStore = useSettingStore()
@@ -29,6 +39,15 @@ let residueWordListRef: any = $ref()
let chapterListRef: any = $ref()
let chapterIndex = $ref(1)
let residueWordList = $ref([])
let isEditDict = $ref(false)
let showExport = $ref(false)
let chapterWordNumber = $ref(settingStore.chapterWordNumber)
useWindowClick(() => showExport = false)
const isPinDict = $computed(() => {
return [DictType.collect, DictType.wrong, DictType.simple].includes(runtimeStore.editDict.type)
})
let chapterWordList: Word[] = $computed({
get() {
@@ -39,15 +58,57 @@ let chapterWordList: Word[] = $computed({
}
})
async function getDictDetail(val: { dict: DictResource | Dict, index: number }) {
let item = val.dict
// console.log('word-getDictDetail', item)
chapterList2 = []
chapterIndex = -1
wordFormData.type = FormMode.None
loading = true
let find: Dict = store.myDictList.find((v: Dict) => v.id === item.id)
if (find) {
runtimeStore.editDict = cloneDeep(find)
} else {
runtimeStore.editDict = cloneDeep({
...cloneDeep(DefaultDict),
...item,
})
runtimeStore.editDict.id = nanoid(6)
//设置默认章节单词数
runtimeStore.editDict.chapterWordNumber = settingStore.chapterWordNumber
}
if ([DictType.collect, DictType.simple, DictType.wrong].includes(runtimeStore.editDict.type)) {
} else {
//如果不是自定义词典并且有url地址才去下载
if (!runtimeStore.editDict.isCustom && runtimeStore.editDict.url) {
let url = `./dicts/${runtimeStore.editDict.language}/${runtimeStore.editDict.type}/${runtimeStore.editDict.translateLanguage}/${runtimeStore.editDict.url}`;
if (runtimeStore.editDict.type === DictType.word) {
if (!runtimeStore.editDict.originWords.length) {
let r = await fetch(url)
let v = await r.json()
v.map(s => {
s.id = nanoid(6)
})
runtimeStore.editDict.originWords = cloneDeep(v)
changeSort(runtimeStore.editDict.sort)
} else {
runtimeStore.editDict.length = runtimeStore.editDict.words.length + runtimeStore.editDict.residueWords.length
}
}
}
}
chapterList2 = Array.from({length: runtimeStore.editDict.chapterWords.length}).map((v, i) => ({id: i}))
loading = false
}
function addNewChapter() {
runtimeStore.editDict.chapterWords.push([])
chapterList2 = Array.from({length: runtimeStore.editDict.chapterWords.length}).map((v, i) => ({id: i}))
chapterListRef?.scrollToItem(chapterList2.length - 1)
ElMessage.success('新增章节成功')
console.log('scrollToBottom', chapterListRef)
setTimeout(() => chapterListRef?.scrollToItem(chapterList2.length -1), 100)
}
function delWordChapter(index: number) {
let list = runtimeStore.editDict.chapterWords[index]
list.map(v => v.checked = false)
@@ -58,10 +119,9 @@ function delWordChapter(index: number) {
if (chapterIndex >= index) chapterIndex--
if (chapterIndex < 0) chapterIndex = 0
syncMyDictList()
syncEditDict2MyDictList()
}
let chapterWordListCheckedTotal = $computed(() => {
return chapterWordList.filter(v => v.checked).length
})
@@ -76,7 +136,6 @@ function handleChangeCurrentChapter(index: number) {
closeWordForm()
}
function checkRepeatWord(
words: Word[],
targetList: Word[],
@@ -109,7 +168,6 @@ function checkRepeatWord(
}
}
function toResidueWordList() {
let list = cloneDeep(chapterWordList.filter(v => v.checked))
list.map(v => v.checked = false)
@@ -183,9 +241,14 @@ function toChapterWordList() {
)
}
//同步到我的词典列表
function syncEditDict2MyDictList() {
syncMyDictList(runtimeStore.editDict)
}
/**/
/* 单词修改相关*/
/* start单词修改相关start*/
/**/
let wordFormData = $ref({
@@ -217,7 +280,6 @@ const wordRules = reactive<FormRules>({
],
})
async function onSubmitWord() {
await wordFormRef.validate((valid, fields) => {
if (valid) {
@@ -252,26 +314,13 @@ async function onSubmitWord() {
if (r) assign(r, data)
ElMessage.success('修改成功')
}
syncMyDictList()
syncEditDict2MyDictList()
} else {
ElMessage.warning('请填写完整')
}
})
}
//同步到我的词典列表
function syncMyDictList() {
//任意修改,都将其变为自定义词典
runtimeStore.editDict.isCustom = true
let rIndex = store.myDictList.findIndex(v => v.id === runtimeStore.editDict.id)
if (rIndex > -1) {
store.myDictList[rIndex] = cloneDeep(runtimeStore.editDict)
} else {
store.myDictList.push(cloneDeep(runtimeStore.editDict))
}
}
function delWord(val: { word: Word }) {
let rIndex = runtimeStore.editDict.originWords.findIndex(v => v.id === val.word.id)
if (rIndex > -1) {
@@ -285,7 +334,7 @@ function delWord(val: { word: Word }) {
if (wordFormData.type === FormMode.Edit && wordForm.name === val.word.name) {
closeWordForm()
}
syncMyDictList()
syncEditDict2MyDictList()
}
function editWord(word: Word, index: number, where: string) {
@@ -298,7 +347,6 @@ function editWord(word: Word, index: number, where: string) {
wordForm.trans = word.trans.join('\n')
}
function addWord(where: string) {
// setTimeout(wordListRef?.scrollToBottom, 100)
wordFormData.type = FormMode.Add
@@ -312,24 +360,10 @@ function closeWordForm() {
}
/**/
/* 单词修改相关*/
/* end单词修改相关end*/
/**/
let showAllocationChapterDialog = $ref(false)
let chapterWordNumber = $ref(settingStore.chapterWordNumber)
function resetChapterList(num?: number) {
if (num !== undefined) {
runtimeStore.editDict.chapterWordNumber = num
}
residueWordList = []
chapterIndex = -1
runtimeStore.editDict.words.map(v => v.checked = false)
runtimeStore.editDict.chapterWords = chunk(runtimeStore.editDict.words, runtimeStore.editDict.chapterWordNumber)
chapterList2 = Array.from({length: runtimeStore.editDict.chapterWords.length}).map((v, i) => ({id: i}))
// console.log('runtimeStore.editDict.chapterWords',runtimeStore.editDict.chapterWords)
// console.log('chapterList2',chapterList2)
}
function changeSort(v: Sort) {
if (v === Sort.normal) {
@@ -342,6 +376,18 @@ function changeSort(v: Sort) {
resetChapterList()
}
function resetChapterList(num?: number) {
if (num !== undefined) {
runtimeStore.editDict.chapterWordNumber = num
}
residueWordList = []
chapterIndex = -1
runtimeStore.editDict.words.map(v => v.checked = false)
runtimeStore.editDict.chapterWords = chunk(runtimeStore.editDict.words, runtimeStore.editDict.chapterWordNumber)
runtimeStore.editDict.length = runtimeStore.editDict.words.length + runtimeStore.editDict.residueWords.length
chapterList2 = Array.from({length: runtimeStore.editDict.chapterWords.length}).map((v, i) => ({id: i}))
}
async function resetDict() {
MessageBox.confirm(
'删除所有自定义内容: 章节、排序、单词,并恢复至默认状态,确认恢复?',
@@ -370,10 +416,21 @@ async function resetDict() {
// runtimeStore.editDict
}
function exportData() {
function exportData(type: string) {
let list = []
let filename = ''
if (type === 'chapter') {
if (chapterIndex === -1) {
return ElMessage.error('请选择章节')
}
list = chapterWordList
filename = runtimeStore.editDict.name + `-第${chapterIndex + 1}`
} else {
list = runtimeStore.editDict.words
filename = runtimeStore.editDict.name
}
let wb = XLSX.utils.book_new()
// 一个简单的sheet
let sheetData = chapterWordList.map(v => {
let sheetData = list.map(v => {
return {
单词: v.name,
'音标①': v.usphone,
@@ -381,11 +438,11 @@ function exportData() {
'释义(一行一个释义)': v.trans.join('\n')
}
})
let sheet = XLSX.utils.json_to_sheet(sheetData)
wb.Sheets['Sheet1'] = sheet
wb.Sheets['Sheet1'] = XLSX.utils.json_to_sheet(sheetData)
wb.SheetNames = ['Sheet1']
XLSX.writeFile(wb, `${runtimeStore.editDict.name}-zh-CN.xlsx`);
ElMessage.success('导出成功!')
XLSX.writeFile(wb, `${filename}-zh-CN.xlsx`);
ElMessage.success(filename + ' 导出成功!')
showExport = false
}
function importData(e: any) {
@@ -432,122 +489,175 @@ function importData(e: any) {
reader.readAsBinaryString(file);
}
defineExpose({getDictDetail})
</script>
<template>
<div class="page-content">
<div class="left-column">
<div class="header">
<div class="common-title">
<span>章节管理</span>
<div class="options">
<BaseIcon
@click="addNewChapter"
icon="fluent:add-20-filled"
title="新增章节"/>
<div class="dict-detail">
<header>
<div class="back" @click.stop="emit('back')">
<Icon icon="octicon:arrow-left-24" width="20"/>
</div>
<div class="left">
<div class="top">
<div class="title">
{{ runtimeStore.editDict.name }}
</div>
<template v-if="!isPinDict">
<BaseIcon icon="tabler:edit" @click='isEditDict = true'/>
<BaseIcon icon="ph:star" @click='no'/>
<BaseButton size="small" v-if="runtimeStore.editDict.isCustom" @click="resetDict">恢复默认</BaseButton>
</template>
<div class="import hvr-grow">
<BaseButton size="small">导入</BaseButton>
<input type="file"
accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
@change="importData">
</div>
<div class="export"
style="position: relative"
@click.stop="null">
<BaseButton size="small" @click="showExport = true">导出</BaseButton>
<MiniDialog
v-model="showExport"
style="width: 80rem;"
>
<div class="mini-row-title">
导出选项
</div>
<div class="mini-row">
<BaseButton size="small" @click="exportData('all')">全部单词</BaseButton>
</div>
<div class="mini-row">
<BaseButton size="small" @click="exportData('chapter')">当前章节</BaseButton>
</div>
</MiniDialog>
</div>
</div>
<div class="select">
<BaseButton size="small" @click="showAllocationChapterDialog = true">智能分配</BaseButton>
<span>{{ runtimeStore.editDict.chapterWords.length }}</span>
</div>
<div class="desc" v-if="runtimeStore.editDict.description">{{ runtimeStore.editDict.description }}</div>
<div class="num">总词汇: {{ runtimeStore.editDict.length }}</div>
</div>
<div class="wrapper">
<RecycleScroller
v-if="chapterList2.length"
ref="chapterListRef"
style="height: 100%;"
:items="chapterList2"
:item-size="63"
key-field="id"
v-slot="{ item,index }"
>
<div style="padding: 0 15rem;">
<div class="common-list-item"
:class="chapterIndex === item.id && 'active'"
@click="handleChangeCurrentChapter(item.id)">
<div class="flex gap10 flex1 ">
<input type="radio" :checked="chapterIndex === item.id">
<div class="item-title flex flex1 space-between">
<span>{{ item.id + 1 }}</span>
<span>{{ runtimeStore.editDict.chapterWords[item.id]?.length }}</span>
</div>
</div>
<div class="right">
<BaseIcon
class-name="del"
@click="delWordChapter(item.id)"
title="移除"
icon="solar:trash-bin-minimalistic-linear"/>
</div>
</header>
<EditDict
:isAdd="false"
v-if="isEditDict"
@cancel="isEditDict = false"
@submit="isEditDict = false"/>
<div v-else class="content">
<div class="left-column">
<div class="header">
<div class="common-title">
<span>章节管理</span>
<div class="options">
<BaseIcon
@click="addNewChapter"
icon="fluent:add-20-filled"
title="新增章节"/>
</div>
</div>
</RecycleScroller>
<Empty v-else/>
</div>
</div>
<ChapterWordList
ref="chapterWordListRef"
:title="`${chapterIndex > -1 ? `第${chapterIndex + 1}章` : ''} 单词列表`"
:show-add="chapterIndex > -1"
@add="addWord('chapter')"
@del="delWord"
:empty-title="chapterIndex === -1?'请选择章节':null"
@edit="val => editWord(val.word,val.index,'chapter')"
v-model:list="chapterWordList"/>
<div class="options-column">
<BaseButton @click="toChapterWordList"
:disabled="residueWordListCheckedTotal === 0">
&lt;
</BaseButton>
<BaseButton @click="toResidueWordList"
:disabled="chapterWordListCheckedTotal === 0">
&gt;
</BaseButton>
</div>
<ChapterWordList
ref="residueWordListRef"
title="未分配单词列表"
:empty-title="null"
:show-add="true"
@add="addWord('residue')"
@del="delWord"
@edit="val => editWord(val.word,val.index,'residue')"
v-model:list="residueWordList"/>
<div class="right-column">
<div class="add" v-if="wordFormData.type">
<div class="common-title">
{{ wordFormData.type === FormMode.Add ? '添加' : '修改' }}单词
{{
wordFormData.type === FormMode.Add ? (wordFormData.where === 'chapter' ? `> 第${chapterIndex + 1}章` : '> 未分配') : ''
}}
</div>
<el-form
class="form"
ref="wordFormRef"
:rules="wordRules"
:model="wordForm"
label-width="100rem">
<el-form-item label="单词" prop="name">
<el-input v-model="wordForm.name"/>
</el-form-item>
<el-form-item label="翻译">
<el-input v-model="wordForm.trans"
placeholder="多个翻译请换行"
:autosize="{ minRows: 2, maxRows: 6 }"
type="textarea"/>
</el-form-item>
<el-form-item label="音标/发音①">
<el-input v-model="wordForm.usphone"/>
</el-form-item>
<el-form-item label="音标/发音②">
<el-input v-model="wordForm.ukphone"/>
</el-form-item>
<div class="flex-center">
<el-button @click="closeWordForm">关闭</el-button>
<el-button type="primary" @click="onSubmitWord">保存</el-button>
<div class="select">
<BaseButton size="small" @click="showAllocationChapterDialog = true">智能分配</BaseButton>
<span>{{ runtimeStore.editDict.chapterWords.length }}</span>
</div>
</el-form>
</div>
<div class="wrapper">
<RecycleScroller
v-if="chapterList2.length"
ref="chapterListRef"
style="height: 100%;"
:items="chapterList2"
:item-size="63"
key-field="id"
v-slot="{ item,index }"
>
<div style="padding: 0 15rem;">
<div class="common-list-item"
:class="chapterIndex === item.id && 'active'"
@click="handleChangeCurrentChapter(item.id)">
<div class="flex gap10 flex1 ">
<input type="radio" :checked="chapterIndex === item.id">
<div class="item-title flex flex1 space-between">
<span>{{ item.id + 1 }}</span>
<span>{{ runtimeStore.editDict.chapterWords[item.id]?.length }}</span>
</div>
</div>
<div class="right">
<BaseIcon
class-name="del"
@click="delWordChapter(item.id)"
title="移除"
icon="solar:trash-bin-minimalistic-linear"/>
</div>
</div>
</div>
</RecycleScroller>
<Empty v-else/>
</div>
</div>
<ChapterWordList
ref="chapterWordListRef"
:title="`${chapterIndex > -1 ? `第${chapterIndex + 1}章` : ''} 单词列表`"
:show-add="chapterIndex > -1"
@add="addWord('chapter')"
@del="delWord"
:empty-title="chapterIndex === -1?'请选择章节':null"
@edit="val => editWord(val.word,val.index,'chapter')"
v-model:list="chapterWordList"/>
<div class="options-column">
<BaseButton @click="toChapterWordList"
:disabled="residueWordListCheckedTotal === 0">
&lt;
</BaseButton>
<BaseButton @click="toResidueWordList"
:disabled="chapterWordListCheckedTotal === 0">
&gt;
</BaseButton>
</div>
<ChapterWordList
ref="residueWordListRef"
title="未分配单词列表"
:empty-title="null"
:show-add="true"
@add="addWord('residue')"
@del="delWord"
@edit="val => editWord(val.word,val.index,'residue')"
v-model:list="residueWordList"/>
<div class="right-column">
<div class="add" v-if="wordFormData.type">
<div class="common-title">
{{ wordFormData.type === FormMode.Add ? '添加' : '修改' }}单词
{{
wordFormData.type === FormMode.Add ? (wordFormData.where === 'chapter' ? `> 第${chapterIndex + 1}章` : '> 未分配') : ''
}}
</div>
<el-form
class="form"
ref="wordFormRef"
:rules="wordRules"
:model="wordForm"
label-width="100rem">
<el-form-item label="单词" prop="name">
<el-input v-model="wordForm.name"/>
</el-form-item>
<el-form-item label="翻译">
<el-input v-model="wordForm.trans"
placeholder="多个翻译请换行"
:autosize="{ minRows: 2, maxRows: 6 }"
type="textarea"/>
</el-form-item>
<el-form-item label="音标/发音①">
<el-input v-model="wordForm.usphone"/>
</el-form-item>
<el-form-item label="音标/发音②">
<el-input v-model="wordForm.ukphone"/>
</el-form-item>
<div class="flex-center">
<el-button @click="closeWordForm">关闭</el-button>
<el-button type="primary" @click="onSubmitWord">保存</el-button>
</div>
</el-form>
</div>
</div>
</div>
</div>
@@ -599,8 +709,61 @@ function importData(e: any) {
</template>
<style scoped lang="scss">
.dict-detail {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
.page-content {
header {
width: 100%;
display: flex;
box-sizing: border-box;
align-items: center;
color: var(--color-font-1);
padding: 0 var(--space);
gap: 20rem;
margin-bottom: 20rem;
.back {
height: 100%;
display: flex;
align-items: center;
cursor: pointer;
}
.left {
display: flex;
gap: 10rem;
flex-direction: column;
color: var(--color-font-2);
.top {
color: var(--color-font-1);
display: flex;
gap: 10rem;
font-size: 20rem;
align-items: center;
}
.import {
display: inline-flex;
position: relative;
input {
position: absolute;
height: 100%;
width: 100%;
opacity: 0;
}
}
}
}
}
.content {
flex: 1;
overflow: hidden;
display: flex;

View File

@@ -193,7 +193,7 @@ export const useBaseStore = defineStore('base', {
try {
let configStr: string = await localforage.getItem(SaveDict.key)
// console.log(configStr)
console.log('s', new Blob([configStr]).size)
// console.log('s', new Blob([configStr]).size)
configStr = ''
if (configStr) {
let data = JSON.parse(configStr)
@@ -214,9 +214,7 @@ export const useBaseStore = defineStore('base', {
} else {
let dictResourceUrl = `./dicts/${this.currentDict.language}/${this.currentDict.type}/${this.currentDict.translateLanguage}/${this.currentDict.url}`;
if ([
DictType.word,
].includes(this.currentDict.type)) {
if ([DictType.word].includes(this.currentDict.type)) {
if (!this.currentDict.originWords.length) {
let r = await fetch(dictResourceUrl)
// let r = await fetch(`.${this.currentDict.url}`)
@@ -239,10 +237,7 @@ export const useBaseStore = defineStore('base', {
}
}
if ([
DictType.article,
DictType.customArticle,
].includes(this.currentDict.type)) {
if ([DictType.article].includes(this.currentDict.type)) {
if (!this.currentDict.articles.length) {
let r = await fetch(dictResourceUrl)
let s: any[] = await r.json()

View File

@@ -224,11 +224,13 @@ export const DefaultDict: Dict = {
words: [],
chapterWordNumber: DefaultChapterWordNumber,//章节单词数量
chapterWords: [],
residueWords: [],//未分配单词
chapterIndex: 0,//章节下标
wordIndex: 0,//单词下标
articles: [],
statistics: [],
isCustom: false,
length: 0,
/*资源属性*/
resourceId: '',
url: '',
@@ -248,11 +250,13 @@ export interface Dict {
words: Word[],
chapterWordNumber: number,//章节单词数量
chapterWords: Word[][],
residueWords: Word[],
chapterIndex: number,//章节下标
wordIndex: number,//单词下标
articles: Article[],
statistics: Statistics[],
isCustom: boolean,
length: number,
/*资源属性*/
resourceId: string,
category: string