Develop dictionary management function

This commit is contained in:
zyronon
2023-11-29 01:57:23 +08:00
parent 2d06e1fc2a
commit 72875a5fc9
18 changed files with 197 additions and 1237 deletions

3
components.d.ts vendored
View File

@@ -10,7 +10,6 @@ declare module 'vue' {
Add: typeof import('./src/components/toolbar/Add.vue')['default']
ArticleList2: typeof import('./src/components/list/ArticleList2.vue')['default']
ArticleList3: typeof import('./src/components/list/ArticleList3.vue')['default']
ArticleListFQ: typeof import('./src/components/article/ArticleList-FQ.vue')['default']
Backgorund: typeof import('./src/components/Backgorund.vue')['default']
BaseButton: typeof import('./src/components/BaseButton.vue')['default']
BaseIcon: typeof import('./src/components/BaseIcon.vue')['default']
@@ -23,10 +22,10 @@ declare module 'vue' {
DictGroup: typeof import('./src/components/toolbar/DictGroup.vue')['default']
DictItem: typeof import('./src/components/list/DictItem.vue')['default']
DictList: typeof import('./src/components/list/DictList.vue')['default']
DictListPanel: typeof import('./src/components/DictListPanel.vue')['default']
EditAbleText: typeof import('./src/components/EditAbleText.vue')['default']
EditArticle: typeof import('./src/components/article/EditArticle.vue')['default']
EditBatchArticleModal: typeof import('./src/components/article/EditBatchArticleModal.vue')['default']
EditBatchArticleModalFQ: typeof import('./src/components/article/EditBatchArticleModal-FQ.vue')['default']
EditSingleArticleModal: typeof import('./src/components/article/EditSingleArticleModal.vue')['default']
ElButton: typeof import('element-plus/es')['ElButton']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']

View File

@@ -1,110 +0,0 @@
<script setup lang="ts">
import Input from "@/components/Input.vue";
import {$computed, $ref} from "vue/macros";
import {Article, Word} from "@/types.ts";
import ListItem from "@/components/list/ListItem.vue";
import {useSettingStore} from "@/stores/setting.ts";
import {watch} from "vue";
const props = withDefaults(defineProps<{
list: Article[],
activeIndex?: number,
isActive?: boolean
}>(), {
activeIndex: -1,
isActive: false
})
const emit = defineEmits<{
selectItem: [val: Article],
delSelectItem: [],
'update:searchKey': [val: string],
'update:list': [list: Article[]],
}>()
let searchKey = $ref('')
let localList = $computed({
get() {
if (searchKey) {
return props.list.filter((item: Article) => {
//把搜索内容,分词之后,判断是否有这个词,比单纯遍历包含体验更好
return searchKey.toLowerCase().split(' ').filter(v => v).some(value => {
return item.title.toLowerCase().includes(value) || item.titleTranslate.toLowerCase().includes(value)
})
})
} else {
return props.list
}
},
set(newValue) {
emit('update:list', newValue)
}
})
const settingStore = useSettingStore()
const listRef: HTMLElement = $ref(null as any)
function scrollViewToCenter(index: number) {
if (index === -1) return
listRef.children[index + 1]?.scrollIntoView({block: 'center', behavior: 'smooth'})
}
watch(() => props.activeIndex, (n: any) => {
if (settingStore.showPanel) {
scrollViewToCenter(n)
}
})
watch(() => props.isActive, (n: boolean) => {
setTimeout(() => {
if (n) scrollViewToCenter(props.activeIndex)
}, 300)
})
watch(() => props.list, () => {
listRef.scrollTo(0, 0)
})
</script>
<template>
<div class="list"
ref="listRef"
>
<div class="search">
<Input v-model="searchKey"/>
</div>
<ListItem
@click="emit('selectItem',item)"
v-for="(item,i) in localList"
:active="activeIndex === i"
:key="item.id">
<div class="name"> {{ `${i + 1}. ${item.title}` }}</div>
<div class="item-translate"> {{ ` ${item.titleTranslate}` }}</div>
</ListItem>
</div>
</template>
<style scoped lang="scss">
@import "@/assets/css/variable.scss";
.list {
display: flex;
flex-direction: column;
gap: 15rem;
flex: 1;
overflow: overlay;
padding: 0 var(--space);
.search {
width: 100%;
}
.translate {
font-size: 16rem;
}
}
</style>

View File

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

View File

@@ -1,13 +1,11 @@
<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, languageCategoryOptions, Sort, Word} from "@/types.ts"
import {chunk, cloneDeep, groupBy, reverse, shuffle} from "lodash-es";
import {onMounted, watch} from "vue"
import {DefaultDict, Dict, DictResource, DictType, Sort} from "@/types.ts"
import {chunk, cloneDeep, reverse, shuffle} from "lodash-es";
import {$computed, $ref} from "vue/macros";
import BaseButton from "@/components/BaseButton.vue";
import {Icon} from '@iconify/vue';
import DictGroup from "@/components/toolbar/DictGroup.vue";
import {ActivityCalendar} from "vue-activity-calendar";
import "vue-activity-calendar/style.css";
import ChapterList from "@/components/list/ChapterList.vue";
@@ -17,22 +15,18 @@ 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 DictList from "@/components/list/DictList.vue";
import VirtualWordList from "@/components/list/VirtualWordList.vue";
import {FormInstance, FormRules} from "element-plus";
import Empty from "@/components/Empty.vue";
import BaseIcon from "@/components/BaseIcon.vue";
import Dialog from "@/components/dialog/Dialog.vue";
import EditBatchArticleModal from "@/components/article/EditBatchArticleModal.vue";
import {nanoid} from "nanoid";
import DictListPanel from "@/components/DictListPanel.vue";
import {useRouter} from "vue-router";
const store = useBaseStore()
const settingStore = useSettingStore()
const runtimeStore = useRuntimeStore()
let currentLanguage = $ref('en')
let currentTranslateLanguage = $ref('common')
let groupByLanguage = groupBy(dictionaryResources, 'language')
let translateLanguageList = $ref([])
let wordList = $ref([])
let router = useRouter()
let step = $ref(1)
let loading = $ref(false)
@@ -44,13 +38,9 @@ function close() {
async function selectDict(val: { dict: DictResource | Dict, index: number }) {
let item = val.dict
console.log('item', item)
// console.log('item', item)
step = 1
isAddDict = false
detailListTabIndex = 0
wordFormMode = FormMode.None
loading = true
wordList = []
let find: Dict = store.myDictList.find((v: Dict) => v.id === item.id)
if (find) {
runtimeStore.editDict = cloneDeep(find)
@@ -62,7 +52,6 @@ async function selectDict(val: { dict: DictResource | Dict, index: number }) {
}
if ([DictType.collect, DictType.simple, DictType.wrong].includes(runtimeStore.editDict.type)) {
wordList = cloneDeep(runtimeStore.editDict.words)
} else {
let url = `./dicts/${runtimeStore.editDict.language}/${runtimeStore.editDict.type}/${runtimeStore.editDict.translateLanguage}/${runtimeStore.editDict.url}`;
if (runtimeStore.editDict.type === DictType.word) {
@@ -72,14 +61,12 @@ async function selectDict(val: { dict: DictResource | Dict, index: number }) {
runtimeStore.editDict.originWords = cloneDeep(v)
changeSort(runtimeStore.editDict.sort)
}
wordList = cloneDeep(runtimeStore.editDict.words)
}
if (runtimeStore.editDict.type === DictType.customWord) {
if (!runtimeStore.editDict.words.length) {
changeSort(runtimeStore.editDict.sort)
}
wordList = cloneDeep(runtimeStore.editDict.words)
}
if (runtimeStore.editDict.type === DictType.article) {
@@ -101,49 +88,6 @@ function changeDict() {
close()
}
function groupByDictTags(dictList: DictResource[]) {
return dictList.reduce<Record<string, DictResource[]>>((result, dict) => {
dict.tags.forEach((tag) => {
if (Object.prototype.hasOwnProperty.call(result, tag)) {
result[tag].push(dict)
} else {
result[tag] = [dict]
}
})
return result
}, {})
}
const groupByTranslateLanguage = $computed(() => {
let data: any
if (currentLanguage === 'article') {
let articleList = dictionaryResources.filter(v => v.type === 'article')
data = groupBy(articleList, 'translateLanguage')
} else if (currentLanguage === 'my') {
data = {
common: store.myDictList.concat([{name: '',} as any])
}
} else {
data = groupBy(groupByLanguage[currentLanguage], 'translateLanguage')
}
// console.log('groupByTranslateLanguage', data)
translateLanguageList = Object.keys(data)
currentTranslateLanguage = translateLanguageList[0]
return data
})
const groupedByCategoryAndTag = $computed(() => {
const currentTranslateLanguageDictList = groupByTranslateLanguage[currentTranslateLanguage]
const groupByCategory = groupBy(currentTranslateLanguageDictList, 'category')
let data = []
for (const [key, value] of Object.entries(groupByCategory)) {
data.push([key, groupByDictTags(value)])
}
// console.log('groupedByCategoryAndTag', data)
return data
})
function clickEvent(e) {
console.log('e', e)
}
@@ -179,256 +123,23 @@ function changeSort(v) {
resetChapterList()
}
let detailListTabIndex = $ref(0)
function changeDetailListTab(val: number) {
detailListTabIndex = val
}
/**/
/*词典相关*/
/**/
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)
show = false
setTimeout(() => {
router.push({path: '/dict', query: {type: 'editDict'}})
}, 500)
}
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('请填写完整')
}
})
}
/**/
/*词典相关*/
/**/
/**/
/* 单词修改相关*/
/**/
enum FormMode {
None = -1,
Add = -2,
}
const DefaultFormWord = {
name: '',
usphone: '',
ukphone: '',
trans: ''
}
let wordFormMode = $ref(FormMode.None)
let wordForm = $ref(cloneDeep(DefaultFormWord))
const wordFormRef = $ref<FormInstance>()
const wordRules = reactive<FormRules>({
name: [
{required: true, message: '请输入单词', trigger: 'blur'},
{max: 30, message: '名称不能超过30个字符', trigger: 'blur'},
],
})
let wordListRef: any = $ref()
//同步到我的词典列表
function syncMyDictList() {
//任意修改,都将其变为自定义词典
if (runtimeStore.editDict.type === DictType.word) runtimeStore.editDict.type = DictType.customWord
if (runtimeStore.editDict.type === DictType.article) runtimeStore.editDict.type = DictType.customArticle
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))
}
}
async function onSubmitWord() {
await wordFormRef.validate((valid, fields) => {
if (valid) {
let data: any = cloneDeep(wordForm)
if (data.trans) {
data.trans = data.trans.split('\n');
} else {
data.trans = []
}
if (wordFormMode === FormMode.Add) {
if (wordList.find(v => v.name === wordForm.name)) {
return ElMessage.warning('已有相同名称单词!')
} else {
runtimeStore.editDict.originWords.push(data)
runtimeStore.editDict.words.push(data)
//因为虚拟列表,必须重新赋值才能检测到更新
wordList = cloneDeep(runtimeStore.editDict.words)
if (runtimeStore.editDict.chapterWords.length) {
runtimeStore.editDict.chapterWords[runtimeStore.editDict.chapterWords.length - 1].push(data)
} else {
runtimeStore.editDict.chapterWords.push([data])
runtimeStore.editDict.chapterIndex = 0
}
ElMessage.success('添加成功')
wordForm = cloneDeep(DefaultFormWord)
setTimeout(wordListRef?.scrollToBottom, 100)
}
console.log('runtimeStore.editDict', runtimeStore.editDict)
} else {
let oldData = cloneDeep(runtimeStore.editDict.words[wordFormMode])
runtimeStore.editDict.words[wordFormMode] = data
//因为虚拟列表,必须重新赋值才能检测到更新
wordList = cloneDeep(runtimeStore.editDict.words)
//同步到原始列表因为word可能是随机的所以需要自己寻找index去修改原始列表
let rIndex = runtimeStore.editDict.originWords.findIndex(v => v.name === oldData.name)
if (rIndex > -1) {
runtimeStore.editDict.originWords[rIndex] = data
}
runtimeStore.editDict.chapterWords = runtimeStore.editDict.chapterWords.map(list => {
let rIndex2 = list.findIndex(v => v.name === oldData.name)
if (rIndex2 > -1) {
list[rIndex2] = data
}
return list
})
console.log('runtimeStore.editDict.chapterWords', runtimeStore.editDict.chapterWords)
ElMessage.success('修改成功')
}
syncMyDictList()
} else {
ElMessage.warning('请填写完整')
}
})
}
function delWord(word: Word, index: number) {
//同步到原始列表因为word可能是随机的所以需要自己寻找index去修改原始列表
let rIndex = runtimeStore.editDict.originWords.findIndex(v => v.name === word.name)
if (rIndex > -1) {
runtimeStore.editDict.originWords.splice(rIndex, 1)
}
runtimeStore.editDict.chapterWords.map(list => {
let rIndex2 = list.findIndex(v => v.name === word.name)
if (rIndex2 > -1) {
list.splice(rIndex2, 1)
}
})
runtimeStore.editDict.chapterWords = runtimeStore.editDict.chapterWords.filter(v => v.length)
if (runtimeStore.editDict.chapterWords.length === 0) runtimeStore.editDict.chapterIndex = -1
else {
if (runtimeStore.editDict.chapterIndex >= runtimeStore.editDict.chapterWords.length) {
runtimeStore.editDict.chapterIndex = runtimeStore.editDict.chapterWords.length - 1
}
}
runtimeStore.editDict.words.splice(index, 1)
wordList = cloneDeep(runtimeStore.editDict.words)
syncMyDictList()
closeWordForm()
}
function editWord(val: { word: Word, index: number }) {
wordFormMode = val.index
wordForm.name = val.word.name
wordForm.ukphone = val.word.ukphone
wordForm.usphone = val.word.usphone
wordForm.trans = val.word.trans.join('\n')
}
function closeWordForm() {
wordFormMode = FormMode.None
wordForm = cloneDeep(DefaultFormWord)
}
function addWord() {
// setTimeout(wordListRef?.scrollToBottom, 100)
detailListTabIndex = 1
wordFormMode = FormMode.Add
wordForm = cloneDeep(DefaultFormWord)
}
function add() {
show = false
setTimeout(() => {
router.push({path: '/dict', query: {type: 'addWordOrArticle'}})
}, 500)
if (dictIsArticle) {
} else {
addWord()
}
}
@@ -437,75 +148,35 @@ function add() {
/**/
/**/
/* 文章修改相关*/
/**/
function delChapter(index: number) {
runtimeStore.editDict.articles.splice(index, 1)
if (runtimeStore.editDict.chapterIndex >= index) runtimeStore.editDict.chapterIndex--
if (runtimeStore.editDict.chapterIndex < 0) runtimeStore.editDict.chapterIndex = 0
syncMyDictList()
}
/**/
/* 文章修改相关*/
/**/
watch(() => step, v => {
if (v === 0) {
closeWordForm()
closeDictForm()
}
})
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
}
})
emitter.on(EventKey.openDictModal, (type: 'detail' | 'list' | 'my' | 'collect' | 'simple') => {
emitter.on(EventKey.openDictModal, (type: 'detail' | 'list' | 'my') => {
if (type === "detail") {
selectDict({dict: store.currentDict, index: 0})
}
if (type === "list") {
currentLanguage = 'en'
// currentLanguage = 'en'
step = 0
}
if (type === "my") {
currentLanguage = 'my'
// currentLanguage = 'my'
step = 0
}
if (type === "collect") {
selectDict({dict: store.collect, index: 0})
wordFormMode = FormMode.Add
addWord()
}
if (type === "simple") {
selectDict({dict: store.simple, index: 0})
addWord()
}
show = true
})
// console.log('categoryList', categoryList)
// console.log('tagList', tagList)
})
function addDict() {
show = false
setTimeout(() => {
router.push({path: '/dict', query: {type: 'addDict'}})
}, 500)
}
</script>
<template>
@@ -515,49 +186,10 @@ onMounted(() => {
:show-close="false">
<div id="DictDialog">
<Slide :slide-count="2" :step="step">
<div class="dict-page">
<header>
<div class="tabs">
<div class="tab"
:class="currentLanguage === item.id && 'active'"
@click="currentLanguage = item.id"
v-for="item in languageCategoryOptions">
<img :src='item.flag'/>
<span>{{ item.name }}</span>
</div>
</div>
<Icon @click="close"
class="hvr-grow pointer"
width="20" color="#929596"
icon="ion:close-outline"/>
</header>
<div class="page-content">
<div class="dict-list-wrapper">
<template v-if="currentLanguage === 'my'">
<DictList
@add="step = 1;isAddDict = true"
@selectDict="selectDict"
:list="groupByTranslateLanguage['common']"/>
</template>
<template v-else>
<div class="translate">
<span>翻译</span>
<el-radio-group v-model="currentTranslateLanguage">
<el-radio-button border v-for="i in translateLanguageList" :label="i">{{ i }}</el-radio-button>
</el-radio-group>
</div>
<DictGroup
v-for="item in groupedByCategoryAndTag"
:select-dict-name="runtimeStore.editDict.resourceId"
@selectDict="selectDict"
@detail="step = 1"
:groupByTag="item[1]"
:category="item[0]"
/>
</template>
</div>
</div>
</div>
<DictListPanel
@add="addDict"
@select-dict="selectDict"
/>
<div class="dict-detail-page">
<header>
<div class="left" @click.stop="step = 0">
@@ -571,7 +203,7 @@ onMounted(() => {
width="20" color="#929596"
icon="ion:close-outline"/>
</header>
<div class="detail" v-if="!isAddDict">
<div class="detail">
<div class="page-content">
<div class="left-column">
<BaseIcon
@@ -591,7 +223,7 @@ onMounted(() => {
}}</span>
</div>
<BaseIcon icon="mi:add"
@click='addWord'
@click='add'
/>
</div>
<div class="text">开始日期-</div>
@@ -604,8 +236,8 @@ onMounted(() => {
</div>
</div>
<div class="center-column">
<div class="setting" v-if="wordFormMode === FormMode.None">
<div class="common-title">学习设置</div>
<div class="common-title">学习设置</div>
<div class="setting">
<template v-if="!dictIsArticle">
<div class="row">
<div class="label">每章单词数</div>
@@ -681,85 +313,16 @@ onMounted(() => {
</div>
</div>
</div>
<div class="add" v-else>
<div class="common-title">{{ wordFormMode === FormMode.Add ? '添加' : '修改' }}单词</div>
<el-form
class="form"
ref="wordFormRef"
:rules="wordRules"
:model="wordForm"
label-width="140rem">
<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">{{
wordFormMode === FormMode.Add ? '添加' : '保存'
}}
</el-button>
</div>
</el-form>
</div>
</div>
<div class="right-column">
<div class="tabs">
<div class="tab"
@click="detailListTabIndex = 0"
:class="detailListTabIndex === 0 && 'active'">
<span>{{ dictIsArticle ? '文章' : '章节' }}列表</span>
</div>
<div class="tab"
v-if="!dictIsArticle"
@click="detailListTabIndex = 1"
:class="detailListTabIndex === 1 && 'active'">
<span>单词列表</span>
</div>
</div>
<template
v-if="detailListTabIndex === 0"
>
<ChapterList
v-if="chapterList"
v-loading="loading"
:is-article="dictIsArticle"
@del="delChapter"
v-model:active-index="runtimeStore.editDict.chapterIndex"
:dict="runtimeStore.editDict"/>
<Empty v-else :show-add="true" @add="add"/>
</template>
<div class="scroll" v-else>
<VirtualWordList
ref="wordListRef"
v-if="wordList.length"
class="word-list"
:is-active="true"
@change="editWord"
:list="wordList"
:activeIndex="wordFormMode">
<template v-slot="{word,index}">
<BaseIcon
class-name="del"
@click="delWord(word,index)"
title="移除"
icon="solar:trash-bin-minimalistic-linear"/>
</template>
</VirtualWordList>
<Empty v-else :show-add="true" @add="addWord"/>
</div>
<div class="common-title">{{ dictIsArticle ? '文章' : '章节' }}列表</div>
<ChapterList
v-if="chapterList"
v-loading="loading"
:is-article="dictIsArticle"
v-model:active-index="runtimeStore.editDict.chapterIndex"
:dict="runtimeStore.editDict"/>
<Empty v-else :show-add="true" @add="add"/>
</div>
</div>
<div v-if="false" class="activity">
@@ -781,62 +344,6 @@ onMounted(() => {
<BaseButton @click="changeDict">切换</BaseButton>
</div>
</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>
</div>
</Slide>
</div>
@@ -848,9 +355,6 @@ onMounted(() => {
<style scoped lang="scss">
@import "@/assets/css/variable";
$modal-mask-bg: rgba(#000, .15);
$radius: 16rem;
$time: 0.3s;
$header-height: 60rem;
#DictDialog {
@@ -864,74 +368,9 @@ $header-height: 60rem;
height: 75vh;
}
.dict-page {
width: 50%;
height: 100%;
$header-height: 60rem;
padding: var(--space);
padding-top: 0;
box-sizing: border-box;
header {
display: flex;
justify-content: space-between;
align-items: center;
height: $header-height;
.tabs {
display: flex;
gap: 20rem;
.tab {
color: var(--color-font-1);
cursor: pointer;
padding: 10rem;
padding-bottom: 5rem;
transition: all .5s;
border-bottom: 2px solid transparent;
display: flex;
align-items: center;
gap: 6rem;
&.active {
border-bottom: 2px solid $main;
}
img {
height: 30rem;
}
}
}
}
.page-content {
display: flex;
height: calc(100% - $header-height);
.dict-list-wrapper {
flex: 1;
overflow: auto;
height: 100%;
padding-right: var(--space);
.translate {
display: flex;
align-items: center;
color: var(--color-font-1);
margin-bottom: 30rem;
& > span {
font-size: 22rem;
}
}
}
}
}
.dict-detail-page {
width: 50%;
height: 100%;
$header-height: 60rem;
box-sizing: border-box;
display: flex;
flex-direction: column;
@@ -1089,23 +528,6 @@ $header-height: 60rem;
padding-right: var(--space);
}
}
.edit-dict {
flex: 1;
width: 100%;
display: flex;
justify-content: center;
.wrapper {
width: 50%;
}
.el-select {
width: 100%;
}
}
}
</style>

View File

@@ -82,11 +82,13 @@ watch(() => props.list, () => {
@click="emit('selectItem',source)"
>
<div class="left">
<div class="item-title">
<div class="name"> {{ `${index + 1}. ${source.title}` }}</div>
</div>
<div class="item-sub-title" v-if="source.titleTranslate && showTranslate">
<div class="item-translate"> {{ ` ${source.titleTranslate}` }}</div>
<div class="title-wrapper">
<div class="item-title">
<div class="name"> {{ `${index + 1}. ${source.title}` }}</div>
</div>
<div class="item-sub-title" v-if="source.titleTranslate && showTranslate">
<div class="item-translate"> {{ ` ${source.titleTranslate}` }}</div>
</div>
</div>
</div>
<div class="right">

View File

@@ -14,7 +14,6 @@ const props = defineProps<{
const emit = defineEmits<{
'update:activeIndex': [index: number]
del: [index: number]
}>()
const runtimeStore = useRuntimeStore()
@@ -43,28 +42,23 @@ function showWordListModal(index: number, item: Word[]) {
<div class="flex gap10">
<input type="radio" :checked="activeIndex === index">
<div class="left">
<template v-if="isArticle">
<div class="item-title"
@click.stop="emitter.emit(EventKey.openArticleListModal,item)"
>{{ index + 1 }}.&nbsp;{{ item.title }}
</div>
<div class="item-sub-title" v-if="item.titleTranslate"> {{ item.titleTranslate }}</div>
</template>
<template v-else>
<div class="item-title"
@click.stop="showWordListModal(index,item)"
>{{ index + 1 }}&nbsp;&nbsp;&nbsp;{{ item.length }}
</div>
</template>
<div class="title-wrapper">
<template v-if="isArticle">
<div class="item-title"
@click.stop="emitter.emit(EventKey.openArticleListModal,item)"
>{{ index + 1 }}.&nbsp;{{ item.title }}
</div>
<div class="item-sub-title" v-if="item.titleTranslate"> {{ item.titleTranslate }}</div>
</template>
<template v-else>
<div class="item-title"
@click.stop="showWordListModal(index,item)"
>{{ index + 1 }}&nbsp;&nbsp;&nbsp;{{ item.length }}
</div>
</template>
</div>
</div>
</div>
<div class="right" v-if="isArticle">
<BaseIcon
class-name="del"
@click="emit('del',index)"
title="移除"
icon="solar:trash-bin-minimalistic-linear"/>
</div>
</div>
</div>
</template>

View File

@@ -59,13 +59,15 @@ const playWordAudio = usePlayWordAudio()
@click="emit('change',{word:source,index})"
>
<div class="left">
<div class="item-title">
<span class="word" :class="!showWord && 'text-shadow'">{{ source.name }}</span>
<span class="phonetic">{{ source.usphone }}</span>
<VolumeIcon class="volume" @click="playWordAudio(source.name)"></VolumeIcon>
</div>
<div class="item-sub-title" v-if="source.trans.length && showTranslate">
<div v-for="item in source.trans">{{ item }}</div>
<div class="title-wrapper">
<div class="item-title">
<span class="word" :class="!showWord && 'text-shadow'">{{ source.name }}</span>
<span class="phonetic">{{ source.usphone }}</span>
<VolumeIcon class="volume" @click="playWordAudio(source.name)"></VolumeIcon>
</div>
<div class="item-sub-title" v-if="source.trans.length && showTranslate">
<div v-for="item in source.trans">{{ item }}</div>
</div>
</div>
</div>
<div class="right">

View File

@@ -76,13 +76,15 @@ defineExpose({scrollToBottom})
@click="emit('change',{word:source,index})"
>
<div class="left">
<div class="item-title">
<span class="word" :class="!showWord && 'text-shadow'">{{ source.name }}</span>
<span class="phonetic">{{ source.usphone }}</span>
<VolumeIcon class="volume" @click="playWordAudio(source.name)"></VolumeIcon>
</div>
<div class="item-sub-title" v-if="source.trans.length && showTranslate">
<div v-for="item in source.trans">{{ item }}</div>
<div class="title-wrapper">
<div class="item-title">
<span class="word" :class="!showWord && 'text-shadow'">{{ source.name }}</span>
<span class="phonetic">{{ source.usphone }}</span>
<VolumeIcon class="volume" @click="playWordAudio(source.name)"></VolumeIcon>
</div>
<div class="item-sub-title" v-if="source.trans.length && showTranslate">
<div v-for="item in source.trans">{{ item }}</div>
</div>
</div>
</div>
<div class="right">

View File

@@ -1,8 +1,7 @@
<script setup lang="ts">
import {useBaseStore} from "@/stores/base.ts"
import {nextTick, onMounted, watch} from "vue"
import {Dict, DictResource, DictType, Sort} from "@/types.ts"
import {cloneDeep, reverse, shuffle} from "lodash-es";
import {Dict, DictResource, DictType} from "@/types.ts"
import {$ref} from "vue/macros";
import "vue-activity-calendar/style.css";
import {useRuntimeStore} from "@/stores/runtime.ts";
@@ -11,27 +10,26 @@ import {emitter, EventKey} from "@/utils/eventBus.ts";
import Slide from "@/components/Slide.vue";
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 DictListPanel from "@/components/DictListPanel.vue";
import EditDict from "@/pages/dict/components/EditDict.vue";
import {useRoute} from "vue-router";
const route = useRoute()
const store = useBaseStore()
const settingStore = useSettingStore()
const runtimeStore = useRuntimeStore()
let detailRef = $ref()
let dictIsArticle = $ref(false)
let step = $ref(1)
let step = $ref(0)
let isAddDict = $ref(false)
async function selectDict(val: {
dict: DictResource | Dict,
index: number
}) {
async function selectDict(val: { dict: DictResource | Dict }, cb?: any) {
step = 1
isAddDict = false
dictIsArticle = val.dict.type === DictType.article
nextTick(() => {
detailRef?.getDictDetail(val)
cb?.()
})
}
@@ -39,58 +37,63 @@ function changeDict() {
store.changeDict(runtimeStore.editDict)
}
function changeSort(v: Sort) {
if (v === Sort.normal) {
runtimeStore.editDict.words = cloneDeep(runtimeStore.editDict.originWords)
} else if (v === Sort.random) {
runtimeStore.editDict.words = shuffle(runtimeStore.editDict.originWords)
} else {
runtimeStore.editDict.words = reverse(runtimeStore.editDict.originWords)
}
// resetChapterList()
}
/**/
/*词典相关*/
/**/
let categoryList = {}
let tagList = {}
/**/
/*词典相关*/
/**/
watch(() => step, v => {
if (v === 0) {
// closeWordForm()
// closeDictForm()
// chapterWordNumber = settingStore.chapterWordNumber
}
})
watch(() => store.load, v => {
if (v) {
selectDict({dict: store.currentDict, index: 0})
// selectDict({dict: store.currentDict, index: 0})
}
})
function addDict() {
step = 1
isAddDict = true
}
function back() {
step = 0
}
function cancelAddDict() {
step = 0
setTimeout(() => {
isAddDict = false
}, 500)
}
onMounted(() => {
selectDict({dict: store.currentDict, index: 0})
// selectDict({dict: store.currentDict, index: 0})
console.log('router.params', route)
switch (route.query.type) {
case 'addDict':
setTimeout(() => {
addDict()
}, 300)
break
case 'addWordOrArticle':
setTimeout(() => {
selectDict({dict: runtimeStore.editDict}, () => {
detailRef?.add()
})
}, 300)
break
case 'editDict':
setTimeout(() => {
selectDict({dict: runtimeStore.editDict}, () => {
detailRef?.editDict()
})
}, 300)
break
}
emitter.on(EventKey.openDictModal, (type: 'detail' | 'list' | 'my' | 'collect' | 'simple') => {
if (type === "detail") {
selectDict({dict: store.currentDict, index: 0})
selectDict({dict: store.currentDict})
}
if (type === "list") {
currentLanguage = 'en'
// currentLanguage = 'en'
step = 0
}
if (type === "my") {
currentLanguage = 'my'
// currentLanguage = 'my'
step = 0
}
if (type === "collect") {
@@ -105,19 +108,6 @@ onMounted(() => {
// console.log('categoryList', categoryList)
// console.log('tagList', tagList)
})
function addDict() {
step = 1
isAddDict = true
}
function back() {
step = 0
// isAddDict = false
}
</script>
<template>
@@ -131,7 +121,7 @@ function back() {
<EditDict
v-if="isAddDict"
:isAdd="true"
@cancel="isAddDict = false;step = 0"
@cancel="cancelAddDict"
@submit="selectDict({dict:runtimeStore.editDict})"/>
<template v-else>
<ArticleDictDetail
@@ -151,11 +141,6 @@ function back() {
<style scoped lang="scss">
@import "@/assets/css/variable";
$modal-mask-bg: rgba(#000, .15);
$radius: 16rem;
$time: 0.3s;
$header-height: 60rem;
#DictDialog {
font-size: 14rem;
position: fixed;

View File

@@ -219,9 +219,15 @@ function exportData(val: { type: string, data?: Article }) {
}
function editDict() {
isEditDict = true
}
defineExpose({getDictDetail})
function add() {
emitter.emit(EventKey.openArticleListModal)
}
defineExpose({getDictDetail, add, editDict})
</script>
@@ -237,7 +243,7 @@ defineExpose({getDictDetail})
{{ runtimeStore.editDict.name }}
</div>
<template v-if="!isPinDict">
<BaseIcon icon="tabler:edit" @click='isEditDict = true'/>
<BaseIcon icon="tabler:edit" @click='editDict'/>
<BaseIcon icon="ph:star" @click='no'/>
<BaseButton size="small" v-if="runtimeStore.editDict.isCustom" @click="resetDict">恢复默认</BaseButton>
</template>
@@ -283,7 +289,7 @@ defineExpose({getDictDetail})
<span>文章管理</span>
<div class="options">
<BaseIcon
@click="emitter.emit(EventKey.openArticleListModal)"
@click="add"
icon="fluent:add-20-filled"
title="新增章节"/>
<span>{{ runtimeStore.editDict.articles.length }}</span>

View File

@@ -16,6 +16,7 @@ const props = defineProps<{
emptyTitle?: string,
showAdd?: boolean,
list: Word[]
canOperation: boolean
}>()
const emit = defineEmits<{
@@ -95,7 +96,7 @@ useWindowClick(() => show = false)
<div class="header">
<div class="common-title">
<span>{{ title }}</span>
<div class="options">
<div class="options" v-if="canOperation">
<div class="setting"
v-if="list.length"
@click.stop="null">
@@ -125,7 +126,7 @@ useWindowClick(() => show = false)
</div>
</div>
<div class="select"
v-if="list.length"
v-if="list.length && canOperation"
>
<div class="left">
<el-checkbox
@@ -145,12 +146,12 @@ useWindowClick(() => show = false)
v-if="list.length"
@click="handleCheckedChange"
>
<template v-slot:prefix="{word}">
<template v-slot:prefix="{word}" v-if="canOperation">
<el-checkbox v-model="word.checked"
@change="handleCheckedChange({word})"
size="large"/>
</template>
<template v-slot="{word,index}">
<template v-slot="{word,index}" v-if="canOperation">
<BaseIcon
class-name="del"
@click="emit('edit',{word,index})"

View File

@@ -9,7 +9,7 @@ import {assign, chunk, cloneDeep, reverse, shuffle} from "lodash-es";
import {DefaultDict, Dict, DictResource, DictType, Sort, Word} from "@/types.ts";
import {nanoid} from "nanoid";
import {FormInstance, FormRules} from "element-plus";
import {reactive, watch} from "vue";
import {reactive} from "vue";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {useBaseStore} from "@/stores/base.ts";
import {useSettingStore} from "@/stores/setting.ts";
@@ -48,6 +48,9 @@ useWindowClick(() => showExport = false)
const isPinDict = $computed(() => {
return [DictType.collect, DictType.wrong, DictType.simple].includes(runtimeStore.editDict.type)
})
const isCanOperation = $computed(() => {
return runtimeStore.editDict.type !== DictType.wrong
})
let chapterWordList: Word[] = $computed({
get() {
@@ -106,7 +109,7 @@ function addNewChapter() {
runtimeStore.editDict.chapterWords.push([])
chapterList2 = Array.from({length: runtimeStore.editDict.chapterWords.length}).map((v, i) => ({id: i}))
ElMessage.success('新增章节成功')
setTimeout(() => chapterListRef?.scrollToItem(chapterList2.length -1), 100)
setTimeout(() => chapterListRef?.scrollToItem(chapterList2.length - 1), 100)
}
function delWordChapter(index: number) {
@@ -490,7 +493,11 @@ function importData(e: any) {
reader.readAsBinaryString(file);
}
defineExpose({getDictDetail})
function editDict() {
isEditDict = true
}
defineExpose({getDictDetail, add: addWord, editDict})
</script>
@@ -506,7 +513,7 @@ defineExpose({getDictDetail})
{{ runtimeStore.editDict.name }}
</div>
<template v-if="!isPinDict">
<BaseIcon icon="tabler:edit" @click='isEditDict = true'/>
<BaseIcon icon="tabler:edit" @click='editDict'/>
<BaseIcon icon="ph:star" @click='no'/>
<BaseButton size="small" v-if="runtimeStore.editDict.isCustom" @click="resetDict">恢复默认</BaseButton>
</template>
@@ -552,13 +559,14 @@ defineExpose({getDictDetail})
<span>章节管理</span>
<div class="options">
<BaseIcon
v-if="isCanOperation"
@click="addNewChapter"
icon="fluent:add-20-filled"
title="新增章节"/>
</div>
</div>
<div class="select">
<BaseButton size="small" @click="showAllocationChapterDialog = true">智能分配</BaseButton>
<BaseButton v-if="isCanOperation" size="small" @click="showAllocationChapterDialog = true">智能分配</BaseButton>
<span>{{ runtimeStore.editDict.chapterWords.length }}</span>
</div>
</div>
@@ -583,7 +591,9 @@ defineExpose({getDictDetail})
<span>{{ runtimeStore.editDict.chapterWords[item.id]?.length }}</span>
</div>
</div>
<div class="right">
<div class="right"
v-if="isCanOperation"
>
<BaseIcon
class-name="del"
@click="delWordChapter(item.id)"
@@ -598,6 +608,7 @@ defineExpose({getDictDetail})
</div>
<ChapterWordList
ref="chapterWordListRef"
:can-operation="isCanOperation"
:title="`${chapterIndex > -1 ? `第${chapterIndex + 1}章` : ''} 单词列表`"
:show-add="chapterIndex > -1"
@add="addWord('chapter')"
@@ -605,7 +616,7 @@ defineExpose({getDictDetail})
:empty-title="chapterIndex === -1?'请选择章节':null"
@edit="val => editWord(val.word,val.index,'chapter')"
v-model:list="chapterWordList"/>
<div class="options-column">
<div class="options-column" v-if="isCanOperation">
<BaseButton @click="toChapterWordList"
:disabled="residueWordListCheckedTotal === 0">
&lt;
@@ -616,6 +627,8 @@ defineExpose({getDictDetail})
</BaseButton>
</div>
<ChapterWordList
v-if="isCanOperation"
:can-operation="isCanOperation"
ref="residueWordListRef"
title="未分配单词列表"
:empty-title="null"

View File

@@ -1,13 +1,20 @@
<script setup lang="ts">
import DictManage from "@/pages/dict/DictManage.vue";
import {onMounted} from "vue";
import {useRoute} from "vue-router";
const router = useRoute()
onMounted(()=>{
})
</script>
<template>
<div id="page">
<DictManage/>
</div>
</template>
<style scoped lang="scss">

View File

@@ -9,7 +9,6 @@ import BaseButton from "@/components/BaseButton.vue";
import {useSettingStore} from "@/stores/setting.ts";
import Close from "@/components/icon/Close.vue";
import Empty from "@/components/Empty.vue";
import ArticleList from "@/components/article/ArticleList-FQ.vue";
import {useArticleOptions, useWordOptions} from "@/hooks/dict.ts";
import {Icon} from "@iconify/vue";
import Tooltip from "@/components/Tooltip.vue";
@@ -18,8 +17,13 @@ import CommonWordList from "@/components/list/CommonWordList.vue";
import BaseIcon from "@/components/BaseIcon.vue";
import {emitter, EventKey} from "@/utils/eventBus.ts";
import ArticleList2 from "@/components/list/ArticleList2.vue";
import {useRouter} from "vue-router";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {cloneDeep} from "lodash-es";
const router = useRouter()
const store = useBaseStore()
const runtimeStore = useRuntimeStore()
const settingStore = useSettingStore()
let tabIndex = $ref(0)
provide('tabIndex', computed(() => tabIndex))
@@ -53,6 +57,16 @@ const {
toggleArticleCollect
} = useArticleOptions()
function addCollect() {
runtimeStore.editDict = cloneDeep(store.collect)
router.push({path: '/dict', query: {type: 'addWordOrArticle'}})
}
function addSimple() {
runtimeStore.editDict = cloneDeep(store.simple)
router.push({path: '/dict', query: {type: 'addWordOrArticle'}})
}
</script>
<template>
<Transition name="fade">
@@ -94,7 +108,7 @@ const {
</div>
<Tooltip title="添加">
<IconWrapper>
<Icon icon="fluent:add-12-regular" @click="emitter.emit(EventKey.openDictModal,'collect')"/>
<Icon icon="fluent:add-12-regular" @click="addCollect"/>
</IconWrapper>
</Tooltip>
</div>
@@ -151,7 +165,7 @@ const {
<div class="dict-name">总词数:{{ store.simple.words.length }}</div>
<Tooltip title="添加">
<IconWrapper>
<Icon icon="fluent:add-12-regular" @click="emitter.emit(EventKey.openDictModal,'simple')"/>
<Icon icon="fluent:add-12-regular" @click="addSimple"/>
</IconWrapper>
</Tooltip>
</div>

View File

@@ -1,16 +1,7 @@
<script setup lang="ts">
import {$ref} from "vue/macros";
import TypingArticle from "./TypingArticle.vue";
import {
Article,
ArticleWord,
DefaultArticle,
DefaultWord,
DisplayStatistics,
ShortcutKey,
TranslateType,
Word
} from "@/types.ts";
import {Article, ArticleWord, DefaultArticle, DisplayStatistics, ShortcutKey, TranslateType, Word} from "@/types.ts";
import {cloneDeep} from "lodash-es";
import TypingWord from "@/pages/practice/practice-word/TypingWord.vue";
import Panel from "../Panel.vue";
@@ -21,7 +12,6 @@ import {useBaseStore} from "@/stores/base.ts";
import EditSingleArticleModal from "@/components/article/EditSingleArticleModal.vue";
import {usePracticeStore} from "@/stores/practice.ts";
import {emitter, EventKey} from "@/utils/eventBus.ts";
import ArticleList from "@/components/article/ArticleList-FQ.vue";
import IconWrapper from "@/components/IconWrapper.vue";
import {Icon} from "@iconify/vue";
import Tooltip from "@/components/Tooltip.vue";

View File

@@ -2,10 +2,10 @@ import * as VueRouter from 'vue-router'
import Practice from "@/pages/practice/index.vue";
import Dict from '@/pages/dict'
const routes = [
const routes: any[] = [
{path: '/practice', component: Practice},
{path: '/dict', component: Dict},
{path: '/', redirect:'/dict'},
{path: '/dict', name: 'dict', component: Dict},
{path: '/', redirect: '/dict'},
]
const router = VueRouter.createRouter({

View File

@@ -84,7 +84,6 @@ export interface Article {
title: string,
titleTranslate: string,
text: string,
textFormat: string,
textCustomTranslate: string,
textCustomTranslateIsFormat: boolean,//翻译是否格式化
textNetworkTranslate: string,
@@ -100,7 +99,6 @@ export const DefaultArticle: Article = {
title: '',
titleTranslate: '',
text: '',
textFormat: '',
textCustomTranslate: '',
textNetworkTranslate: '',
textCustomTranslateIsFormat: false,