This commit is contained in:
zyronon
2023-11-14 18:55:06 +08:00
parent b77148ac0f
commit 4fdfe751e8
10 changed files with 105 additions and 723 deletions

View File

@@ -1,567 +0,0 @@
<script setup lang="ts">
import {$computed, $ref} from "vue/macros";
import Slide from "@/components/Slide.vue";
import DictList from "@/components/list/DictList.vue";
import {Icon} from "@iconify/vue";
import {useBaseStore} from "@/stores/base.ts";
import {useSettingStore} from "@/stores/setting.ts";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {useDisableEventListener} from "@/hooks/event.ts";
import {DefaultDict, Dict, DictType, Word} from "@/types.ts";
import {onMounted, reactive, watch} from "vue";
import {FormInstance, FormRules} from "element-plus";
import {dictionaryResources} from "@/assets/dictionary.ts";
import {cloneDeep} from "lodash-es";
import Empty from "@/components/Empty.vue";
import BaseIcon from "@/components/BaseIcon.vue";
import VirtualWordList from "@/components/list/VirtualWordList.vue";
import Modal from "@/components/Modal/Modal.vue";
import {emitter, EventKey} from "@/utils/eventBus.ts";
const store = useBaseStore()
const emit = defineEmits([
'close',
])
let step = $ref(1)
let isAddDict = $ref(false)
let wordList = $ref([])
let dictList: Dict[] = $computed(() => {
return [
store.collect,
store.simple,
store.wrong
].concat(store.myDicts.filter(v => [DictType.customWord, DictType.customArticle].includes(v.type)))
.concat([{name: '',} as any])
})
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 = []))
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
}
})
// console.log('categoryList', categoryList)
// console.log('tagList', tagList)
})
function selectDict(val: { index: number }) {
store.current.editIndex = val.index
wordList = cloneDeep(store.editDict.originWords)
isAddDict = false
step = 1
}
function editDict() {
// dictForm.id = store.editDict.id
// dictForm.name = store.editDict.name
// dictForm.description = store.editDict.description
console.log('store.editDict', store.editDict)
dictForm = cloneDeep(store.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.id) {
let rIndex = store.myDicts.findIndex(v => v.id === data.id)
store.myDicts[rIndex] = cloneDeep(data)
isAddDict = false
ElMessage.success('修改成功')
} else {
data.id = 'custom-dict-' + Date.now()
if (store.myDicts.find(v => v.name === dictForm.name)) {
return ElMessage.warning('已有相同名称词典!')
} else {
store.myDicts.push(cloneDeep(data))
store.current.editIndex = 3 + store.myDicts.filter(v => [DictType.customWord, DictType.customArticle].includes(v.type)).length - 1
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()
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 {
store.editDict.originWords.push(data)
//因为虚拟列表,必须重新赋值才能检测到更新
wordList = cloneDeep(store.editDict.originWords)
ElMessage.success('添加成功')
wordForm = cloneDeep(DefaultFormWord)
setTimeout(wordListRef?.scrollToBottom, 100)
}
console.log('store.editDict', store.editDict)
} else {
store.editDict.originWords[wordFormMode] = data
//因为虚拟列表,必须重新赋值才能检测到更新
wordList = cloneDeep(store.editDict.originWords)
ElMessage.success('修改成功')
}
} else {
ElMessage.warning('请填写完整')
}
})
}
function delWord(index: number) {
store.editDict.originWords.splice(index, 1)
wordList = cloneDeep(store.editDict.originWords)
}
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() {
wordFormMode = FormMode.Add
wordForm = cloneDeep(DefaultFormWord)
}
watch(() => step, v => {
if (v === 0) {
closeWordForm()
closeDictForm()
}
})
let show = $ref(false)
useDisableEventListener(() => show)
function close() {
show = false
}
onMounted(() => {
emitter.on(EventKey.editDict, (dict: Dict) => {
let rIndex = dictList.findIndex(v => v.id === dict.id)
if (rIndex > -1) {
selectDict({index: rIndex})
addWord()
show = true
}
})
})
</script>
<template>
<Modal
:header="false"
v-model="show"
:show-close="false">
<div id="AddWordDialog" :class="wordFormMode !== FormMode.None && 'add-word-mode'">
<Slide :slide-count="2" :step="step">
<div class="page dict-list-page">
<header>
<div class="title">
我的词典
</div>
<Icon @click="close"
class="hvr-grow pointer"
width="20" color="#929596"
icon="ion:close-outline"/>
</header>
<div class="list">
<DictList
@add="step = 1;isAddDict = true"
@selectDict="selectDict"
:list="dictList"/>
</div>
</div>
<div class="page add-page">
<header>
<div class="left" @click.stop="step = 0">
<Icon icon="octicon:arrow-left-24" class="go" width="20"/>
<div class="title">
词典详情
</div>
</div>
<Icon @click="close"
class="hvr-grow pointer"
width="20" color="#929596"
icon="ion:close-outline"/>
</header>
<div class="detail" v-if="!isAddDict">
<div class="dict">
<div class="info">
<div class="name">{{ store.editDict.name }}</div>
<div class="desc">{{ store.editDict.description }}</div>
<div class="more-info">
<div class="item">词汇量{{ store.editDict.originWords.length }}</div>
<div class="item">创建日期-</div>
<div class="item">花费时间-</div>
<div class="item">累积错误-</div>
</div>
<BaseIcon
v-if="![DictType.collect,DictType.wrong,DictType.simple].includes(store.editDict.type)"
class-name="edit-icon"
icon="tabler:edit"
@click='editDict'
/>
</div>
<div class="add" v-if="wordFormMode !== FormMode.None">
<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="list-wrapper">
<div class="list-header">
<div class="name">单词列表</div>
<div class="flex-center gap10">
<div class="name">{{ wordList.length }}个单词</div>
<BaseIcon icon="mi:add"
@click='addWord'
/>
</div>
</div>
<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(index)"
title="移除"
icon="solar:trash-bin-minimalistic-linear"/>
</template>
</VirtualWordList>
<Empty v-else/>
</div>
</div>
<div class="edit" v-else>
<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>
</Slide>
</div>
</Modal>
</template>
<style scoped lang="scss">
@import "@/assets/css/variable";
#AddWordDialog {
width: 650rem;
height: 70vh;
transition: all .3s;
&.add-word-mode {
width: 800rem;
.more-info {
display: grid;
grid-template-columns: repeat(2, 1fr);
display: none;
}
.dict{
padding-right: var(--space);
}
}
$header-height: 60rem;
header {
color: var(--color-font-3);
display: flex;
justify-content: space-between;
align-items: center;
height: $header-height;
.left {
cursor: pointer;
display: flex;
gap: 10rem;
align-items: center;
}
}
.dict-list-page {
padding: 0 var(--space);
box-sizing: border-box;
}
.add-page {
color: var(--color-font-1);
//display: flex;
//flex-direction: column;
header {
padding: 0 var(--space);
}
.detail {
flex: 1;
height: calc(100% - $header-height);
display: flex;
position: relative;
.dict {
flex: 1;
border-radius: 10rem;
background: var(--color-second-bg);
color: var(--color-font-1);
padding-left: var(--space);
padding-bottom: var(--space);
box-sizing: border-box;
overflow: auto;
.info {
border-radius: 8rem;
background: var(--color-item-bg);
padding: 20rem;
position: relative;
:deep(.edit-icon) {
position: absolute;
top: 10rem;
right: 10rem;
}
.name {
font-size: 24rem;
margin-bottom: 10rem;
}
.desc {
font-size: 18rem;
margin-bottom: 30rem;
}
.item {
margin-top: 10rem;
}
}
.add {
margin-top: 20rem;
}
}
.list-wrapper {
width: 400rem;
display: flex;
flex-direction: column;
font-size: 14rem;
padding-bottom: 20rem;
.list-header {
min-height: 50rem;
padding: 10rem 24rem;
box-sizing: border-box;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 16rem;
color: var(--color-font-3);
.name {
font-size: 18rem;
}
}
}
}
.edit {
height: calc(100% - $header-height);
padding: var(--space);
padding-top: 0;
width: 100%;
box-sizing: border-box;
overflow: auto;
}
}
}
</style>

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import {dictionaryResources} from '@/assets/dictionary.ts'
import {useBaseStore} from "@/stores/base.ts"
import {watch, reactive, onMounted} from "vue"
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 {$computed, $ref} from "vue/macros";
@@ -40,14 +40,6 @@ watch(() => runtimeStore.showDictModal, (n: boolean) => {
runtimeStore.editDict = cloneDeep(store.currentDict)
})
let myAllDict = $computed(() => {
return [
store.collect,
store.simple,
store.wrong
].concat(store.myDicts);
})
async function selectDict(val: { dict: DictResource, index: number }) {
let item = val.dict
console.log('item', item)
@@ -59,44 +51,40 @@ async function selectDict(val: { dict: DictResource, index: number }) {
let find: Dict = store.myDicts.find((v: Dict) => v.name === item.name)
if (find) {
runtimeStore.editDict = cloneDeep(find)
if (find.type === DictType.article) {
if (!find.articles.length) {
let r = await fetch(`./dicts/${find.language}/${find.type}/${find.translateLanguage}/${find.url}`)
let v = await r.json()
runtimeStore.editDict.articles = v.map(s => {
s.id = uuidv4()
return s
})
}
} else {
wordList = cloneDeep(runtimeStore.editDict.originWords)
}
loading = false
} else {
let data: Dict = {
runtimeStore.editDict = cloneDeep({
...cloneDeep(DefaultDict),
...item,
})
}
if ([DictType.collect, DictType.simple, DictType.simple].includes(runtimeStore.editDict.type)) {
wordList = cloneDeep(runtimeStore.editDict.originWords)
} else {
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()
runtimeStore.editDict.originWords = cloneDeep(v)
runtimeStore.editDict.words = cloneDeep(v)
runtimeStore.editDict.chapterWords = chunk(runtimeStore.editDict.words, runtimeStore.editDict.chapterWordNumber)
}
wordList = cloneDeep(runtimeStore.editDict.originWords)
}
runtimeStore.editDict = cloneDeep(data)
let r = await fetch(`./dicts/${data.language}/${data.type}/${data.translateLanguage}/${item.url}`)
r.json().then(v => {
console.log('v', v)
if (data.type === DictType.article) {
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
}))
} else {
runtimeStore.editDict.originWords = v
runtimeStore.editDict.words = v
runtimeStore.editDict.chapterWords = chunk(v, runtimeStore.editDict.chapterWordNumber)
console.log(' runtimeStore.editDict', runtimeStore.editDict)
wordList = cloneDeep(runtimeStore.editDict.originWords)
}
loading = false
})
}
}
loading = false
}
function changeDict() {
@@ -128,12 +116,12 @@ const groupByTranslateLanguage = $computed(() => {
data = groupBy(articleList, 'translateLanguage')
} else if (currentLanguage === 'my') {
data = {
common: myAllDict.concat([{name: '',} as any])
common: store.myDicts.concat([{name: '',} as any])
}
} else {
data = groupBy(groupByLanguage[currentLanguage], 'translateLanguage')
}
console.log('groupByTranslateLanguage', data)
// console.log('groupByTranslateLanguage', data)
translateLanguageList = Object.keys(data)
currentTranslateLanguage = translateLanguageList[0]
return data
@@ -147,7 +135,7 @@ const groupedByCategoryAndTag = $computed(() => {
for (const [key, value] of Object.entries(groupByCategory)) {
data.push([key, groupByDictTags(value)])
}
console.log('groupedByCategoryAndTag', data)
// console.log('groupedByCategoryAndTag', data)
return data
})
@@ -270,6 +258,7 @@ async function onSubmit() {
if (data.id) {
let rIndex = store.myDicts.findIndex(v => v.id === data.id)
store.myDicts[rIndex] = cloneDeep(data)
runtimeStore.editDict = cloneDeep(data)
isAddDict = false
ElMessage.success('修改成功')
} else {
@@ -278,13 +267,12 @@ async function onSubmit() {
return ElMessage.warning('已有相同名称词典!')
} else {
store.myDicts.push(cloneDeep(data))
store.current.editIndex = 3 + store.myDicts.filter(v => [DictType.customWord, DictType.customArticle].includes(v.type)).length - 1
runtimeStore.editDict = cloneDeep(data)
isAddDict = false
ElMessage.success('添加成功')
}
}
console.log('submit!', data)
} else {
ElMessage.warning('请填写完整')
}
@@ -408,7 +396,8 @@ watch(() => step, v => {
<div class="dict-list-wrapper">
<template v-if="currentLanguage === 'my'">
<DictList
@add="step = 1"
@add="step = 1;isAddDict = true"
@selectDict="selectDict"
:list="groupByTranslateLanguage['common']"/>
</template>
<template v-else>
@@ -824,7 +813,6 @@ $header-height: 60rem;
overflow: hidden;
display: flex;
position: relative;
gap: var(--space);
.left-column {
overflow: auto;
@@ -834,11 +822,10 @@ $header-height: 60rem;
gap: 10rem;
min-height: 100rem;
position: relative;
border-radius: 10rem;
background: var(--color-second-bg);
color: var(--color-font-1);
font-size: 14rem;
position: relative;
padding-right: var(--space);
.name {
font-size: 28rem;
@@ -861,8 +848,8 @@ $header-height: 60rem;
:deep(.edit-icon) {
position: absolute;
top: 10rem;
right: 10rem;
top: 8rem;
right: 0;
}
}
@@ -957,7 +944,11 @@ $header-height: 60rem;
justify-content: center;
.wrapper {
width: 70%;
width: 50%;
}
.el-select {
width: 100%;
}
}

View File

@@ -91,7 +91,7 @@ const {
{{ store.collect.articles.length }}篇文章
</div>
</div>
<template v-if="store.current.dictType !== DictType.collect &&
<template v-if="store.currentDict.type !== DictType.collect &&
(
( practiceType === DictType.word && store.collect.words.length) ||
( practiceType === DictType.article && store.collect.articles.length)
@@ -133,7 +133,7 @@ const {
<div class="panel-page-item" v-if="store.simple.words.length">
<div class="list-header">
<div class="dict-name">总词数:{{ store.simple.words.length }}</div>
<template v-if="store.current.dictType !== DictType.simple && store.simple.words.length">
<template v-if="store.currentDict.type !== DictType.simple && store.simple.words.length">
<PopConfirm
:title="`确认切换?`"
@confirm="changeIndex(0,store.simple)"
@@ -161,7 +161,7 @@ const {
<div class="list-header">
<div class="dict-name">总词数:{{ store.wrong.words.length }}</div>
<template
v-if="store.current.dictType !== DictType.wrong && store.wrong.words.length">
v-if="store.currentDict.type !== DictType.wrong && store.wrong.words.length">
<PopConfirm
:title="`确认切换?`"
@confirm="changeIndex(0,store.wrong)"

View File

@@ -17,7 +17,6 @@ import {ShortcutKey} from "@/types.ts";
import useTheme from "@/hooks/useTheme.ts";
import SettingModal from "@/components/Modal/SettingModal.vue";
import DictModal from "@/components/Modal/DictDialog/index.vue";
import AddWordDialog from "@/components/Modal/AddWordDialog.vue";
const practiceStore = usePracticeStore()
const store = useBaseStore()
@@ -165,7 +164,7 @@ onUnmounted(() => {
<PracticeWord ref="practiceRef" v-else/>
<Footer/>
</div>
<AddWordDialog></AddWordDialog>
<!-- <AddWordDialog></AddWordDialog>-->
<DictModal/>
<SettingModal v-if="runtimeStore.showSettingModal" @close="runtimeStore.showSettingModal = false"/>
<Statistics/>

View File

@@ -40,7 +40,7 @@ let editArticle = $ref<Article>(cloneDeep(DefaultArticle))
watch([
() => store.current.index,
() => store.load,
() => store.current.dictType,
() => store.currentDict.type,
() => store.currentDict.chapterIndex,
], n => {
console.log('n', n)

View File

@@ -140,7 +140,7 @@ function next(isTyping: boolean = true) {
data.index++
isTyping && practiceStore.inputWordNumber++
console.log('这个词完了')
if ([DictType.customWord, DictType.word].includes(store.current.dictType)
if ([DictType.customWord, DictType.word].includes(store.currentDict.type)
&& store.skipWordNames.includes(word.name.toLowerCase())) {
next()
}

View File

@@ -9,7 +9,7 @@ defineProps<{
}>()
const emit = defineEmits<{
selectDict: [val: { dict: Dict, index: number }]
selectDict: [val: { dict: any, index: number }]
detail: [],
add: []
}>()