Develop dictionary management function
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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%;
|
||||
|
||||
190
src/pages/dict/components/EditDict.vue
Normal file
190
src/pages/dict/components/EditDict.vue
Normal 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>
|
||||
@@ -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">
|
||||
<
|
||||
</BaseButton>
|
||||
<BaseButton @click="toResidueWordList"
|
||||
:disabled="chapterWordListCheckedTotal === 0">
|
||||
>
|
||||
</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">
|
||||
<
|
||||
</BaseButton>
|
||||
<BaseButton @click="toResidueWordList"
|
||||
:disabled="chapterWordListCheckedTotal === 0">
|
||||
>
|
||||
</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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user