Override list component

This commit is contained in:
zyronon
2023-11-08 17:18:51 +08:00
parent 99cc02bc3d
commit bccd939414
18 changed files with 326 additions and 94 deletions

13
components.d.ts vendored
View File

@@ -15,10 +15,10 @@ declare module 'vue' {
BaseButton: typeof import('./src/components/BaseButton.vue')['default']
BaseIcon: typeof import('./src/components/BaseIcon.vue')['default']
ChapterDetail: typeof import('./src/components/ChapterDetail.vue')['default']
ChapterList: typeof import('./src/components/ChapterList.vue')['default']
ChapterList: typeof import('./src/components/list/ChapterList.vue')['default']
Close: typeof import('./src/components/icon/Close.vue')['default']
DictGroup: typeof import('./src/components/Toolbar/DictGroup.vue')['default']
DictList: typeof import('./src/components/DictList.vue')['default']
DictList: typeof import('./src/components/list/DictList.vue')['default']
DictModal: typeof import('./src/components/Modal/DictModal.vue')['default']
EditAbleText: typeof import('./src/components/EditAbleText.vue')['default']
EditArticle: typeof import('./src/components/Article/EditArticle.vue')['default']
@@ -43,8 +43,9 @@ declare module 'vue' {
Footer: typeof import('./src/components/Practice/Footer.vue')['default']
IconWrapper: typeof import('./src/components/IconWrapper.vue')['default']
Input: typeof import('./src/components/Input.vue')['default']
List: typeof import('./src/components/List.vue')['default']
ListItem: typeof import('./src/components/ListItem.vue')['default']
List: typeof import('./src/components/list/List.vue')['default']
ListItem: typeof import('./src/components/list/ListItem.vue')['default']
ListItem2: typeof import('./src/components/list/ListItem2.vue')['default']
MiniModal: typeof import('./src/components/Modal/MiniModal.vue')['default']
Modal: typeof import('./src/components/Modal/Modal.vue')['default']
Options: typeof import('./src/components/Practice/Options.vue')['default']
@@ -66,7 +67,9 @@ declare module 'vue' {
TypingWord: typeof import('./src/components/Practice/PracticeWord/TypingWord.vue')['default']
VolumeIcon: typeof import('./src/components/icon/VolumeIcon.vue')['default']
VolumeSetting: typeof import('./src/components/Toolbar/VolumeSetting.vue')['default']
WordList: typeof import('./src/components/WordList.vue')['default']
WordItem: typeof import('./src/components/list/WordItem.vue')['default']
WordList: typeof import('./src/components/list/WordList.vue')['default']
WordList2: typeof import('./src/components/list/WordList2.vue')['default']
WordListModal: typeof import('./src/components/Modal/WordListModal.vue')['default']
}
}

View File

@@ -1,9 +1,9 @@
<script setup lang="ts">
import WordList from "@/components/WordList.vue";
import WordList from "@/components/list/WordList.vue";
import {$computed, $ref} from "vue/macros";
import Slide from "@/components/Slide.vue";
import DictList from "@/components/DictList.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";
@@ -15,7 +15,7 @@ import {FormInstance, FormRules} from "element-plus";
import {dictionaryResources} from "@/assets/dictionary.ts";
import {cloneDeep} from "lodash-es";
import {emitter, EventKey} from "@/utils/eventBus.ts";
import ChapterList from "@/components/ChapterList.vue";
import ChapterList from "@/components/list/ChapterList.vue";
let data = $ref({
words: [],

View File

@@ -3,7 +3,7 @@
import Input from "@/components/Input.vue";
import {$computed, $ref} from "vue/macros";
import {Article, Word} from "@/types.ts";
import ListItem from "@/components/ListItem.vue";
import ListItem from "@/components/list/ListItem.vue";
import {useSettingStore} from "@/stores/setting.ts";
import {watch} from "vue";

View File

@@ -7,7 +7,7 @@ import {cloneDeep} from "lodash-es";
import BaseIcon from "@/components/BaseIcon.vue";
import {useBaseStore} from "@/stores/base.ts";
import {$computed, $ref} from "vue/macros";
import List from "@/components/List.vue";
import List from "@/components/list/List.vue";
import {v4 as uuidv4} from 'uuid';
import Modal from "@/components/Modal/Modal.vue";
import EditArticle from "@/components/Article/EditArticle.vue";

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import {Icon} from "@iconify/vue";
import {computed, inject} from "vue"
import WordList from "@/components/WordList.vue"
import WordList from "@/components/list/WordList.vue"
import {useBaseStore} from "@/stores/base.ts"
const store = useBaseStore()

View File

@@ -1,29 +1,31 @@
<script setup lang="ts">
import WordList from "@/components/WordList.vue";
import WordList from "@/components/list/WordList.vue";
import {$computed, $ref} from "vue/macros";
import Slide from "@/components/Slide.vue";
import DictList from "@/components/DictList.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 {Dict, DictType, languageCategoryOptions, Sort} from "@/types.ts";
import {Dict, DictType, languageCategoryOptions, Sort, 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 {emitter, EventKey} from "@/utils/eventBus.ts";
import ChapterList from "@/components/ChapterList.vue";
import Empty from "@/components/Empty.vue";
import BaseIcon from "@/components/BaseIcon.vue";
import WordList2 from "@/components/list/WordList2.vue";
import {usePlayWordAudio} from "@/hooks/sound.ts";
import {useWordOptions} from "@/hooks/dict.ts";
let data = $ref({
words: [],
index: 0
index: -1
})
useDisableEventListener()
const store = useBaseStore()
const settingStore = useSettingStore()
@@ -33,10 +35,8 @@ const emit = defineEmits([
'close',
])
let step = $ref(0)
let isEdit = $ref(true)
useDisableEventListener()
let step = $ref(1)
let isAddDict = $ref(false)
let list = $computed(() => {
return store.myDicts.filter(v => v.type === DictType.customArticle)
@@ -48,7 +48,11 @@ let list = $computed(() => {
.concat([{name: '',} as any])
})
let form = reactive({
let languageCategoryList = []
let categoryList = {}
let tagList = {}
let dictForm = reactive({
id: '',
name: '123',
description: '',
@@ -57,19 +61,8 @@ let form = reactive({
languageCategory: '',
language: '',
})
let wordForm = reactive({
name: '',
trans: '',
usphone: '',
ukphone: '',
})
let languageCategoryList = []
let categoryList = {}
let tagList = {}
const ruleFormRef = $ref<FormInstance>()
const rules = reactive<FormRules>({
const dictFormRef = $ref<FormInstance>()
const dictRules = reactive<FormRules>({
name: [
{required: true, message: '请输入名称', trigger: 'blur'},
{max: 20, message: '名称不能超过20个字符', trigger: 'blur'},
@@ -78,15 +71,9 @@ const rules = reactive<FormRules>({
tags: [{required: true, message: '请选择', trigger: 'change'}],
languageCategory: [{required: true, message: '请选择', trigger: 'change'}],
})
const rules2 = reactive<FormRules>({
name: [
{required: true, message: '请输入单词', trigger: 'blur'},
{max: 30, message: '名称不能超过30个字符', trigger: 'blur'},
],
})
watch(() => form.languageCategory, () => form.category = '')
watch(() => form.category, () => form.tags = [])
watch(() => dictForm.languageCategory, () => dictForm.category = '')
watch(() => dictForm.category, () => dictForm.tags = [])
onMounted(() => {
dictionaryResources.map(v => {
@@ -113,13 +100,14 @@ onMounted(() => {
})
function selectDict(dict: Dict) {
runtimeStore.editDict = cloneDeep(dict)
isEdit = false
// runtimeStore.editDict = cloneDeep(dict)
runtimeStore.editDict = dict
isAddDict = false
step = 1
}
async function onSubmit() {
await ruleFormRef.validate((valid, fields) => {
await dictFormRef.validate((valid, fields) => {
if (valid) {
let data = {
sort: Sort.normal,
@@ -133,20 +121,20 @@ async function onSubmit() {
statistics: [],
articles: [],
url: '',
...form,
...dictForm,
}
if (form.id) {
let rIndex = store.myDicts.findIndex(v => v.id === form.id)
if (dictForm.id) {
let rIndex = store.myDicts.findIndex(v => v.id === dictForm.id)
runtimeStore.editDict = data
store.myDicts[rIndex] = cloneDeep(data)
isEdit = false
isAddDict = false
} else {
if (store.myDicts.find(v => v.name === form.name)) {
if (store.myDicts.find(v => v.name === dictForm.name)) {
return ElMessage.warning('已有相同名称词典!')
} else {
runtimeStore.editDict = data
store.myDicts.push(cloneDeep(data))
isEdit = false
isAddDict = false
console.log('submit!', data)
}
}
@@ -156,16 +144,74 @@ async function onSubmit() {
})
}
function editDict() {
dictForm.name = runtimeStore.editDict.name
dictForm.description = runtimeStore.editDict.description
wordFormMode = FormMode.None;
isAddDict = true
}
enum FormMode {
None = 'None',
Edit = 'Edit',
Add = 'Add'
}
let wordFormMode = $ref(FormMode.None)
let wordForm = reactive({
name: '',
trans: '',
usphone: '',
ukphone: '',
})
const wordFormRef = $ref<FormInstance>()
const wordRules = reactive<FormRules>({
name: [
{required: true, message: '请输入单词', trigger: 'blur'},
{max: 30, message: '名称不能超过30个字符', trigger: 'blur'},
],
})
async function onSubmitWord() {
await wordFormRef.validate((valid, fields) => {
if (valid) {
if (wordFormMode === FormMode.Add) {
if (runtimeStore.editDict.originWords.find(v => v.name === wordForm.name)) {
return ElMessage.warning('已有相同名称单词!')
} else {
let data: any = cloneDeep(wordForm)
if (data.trans) {
data.trans = data.trans.split('\n');
} else {
data.trans = []
}
runtimeStore.editDict.originWords.push(data)
}
console.log('runtimeStore.editDict', runtimeStore.editDict)
}
} else {
ElMessage.warning('请填写完整')
}
})
}
function close() {
emit('close')
}
let isAddWord = $ref(false)
const playWordAudio = usePlayWordAudio()
const {
isWordCollect,
toggleWordCollect,
isWordSimple,
toggleWordSimple,
} = useWordOptions()
</script>
<template>
<div id="AddWordDialog" :class="isAddWord && 'add-word-mode'">
<Slide slide-count="2" :step="step">
<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">
@@ -178,7 +224,7 @@ let isAddWord = $ref(false)
</header>
<div class="list">
<DictList
@add="step = 1;isEdit = true"
@add="step = 1;isAddDict = true"
@selectDict="selectDict"
:list="list"/>
</div>
@@ -196,7 +242,7 @@ let isAddWord = $ref(false)
width="20" color="#929596"
icon="ion:close-outline"/>
</header>
<div class="detail" v-if="!isEdit">
<div class="detail" v-if="!isAddDict">
<div class="dict">
<div class="info">
<div class="name">{{ runtimeStore.editDict.name }}</div>
@@ -210,15 +256,15 @@ let isAddWord = $ref(false)
<BaseIcon
class-name="edit-icon"
icon="tabler:edit"
@click='isAddWord = false;isEdit = true'
@click='editDict'
/>
</div>
<div class="add" v-if="isAddWord">
<div class="add" v-if="wordFormMode !== FormMode.None">
<div class="common-title">添加单词</div>
<el-form
class="form"
ref="ruleFormRef"
:rules="rules"
ref="wordFormRef"
:rules="wordRules"
:model="wordForm"
label-width="140rem">
<el-form-item label="单词" prop="name">
@@ -237,66 +283,86 @@ let isAddWord = $ref(false)
<el-input v-model="wordForm.ukphone"/>
</el-form-item>
<div class="flex-center">
<el-button @click="isAddWord = false">关闭</el-button>
<el-button type="primary" @click="onSubmit">确定</el-button>
<el-button @click="wordFormMode = FormMode.None">关闭</el-button>
<el-button type="primary" @click="onSubmitWord">确定</el-button>
</div>
</el-form>
</div>
</div>
<div class="list">
<div class="list-wrapper">
<div class="list-header">
<div class="name">单词列表</div>
<div class="flex-center gap10">
<div class="name">{{ runtimeStore.editDict.words.length }}个单词</div>
<div class="name">{{ store.currentDict.originWords.length }}个单词</div>
<BaseIcon icon="mi:add"
@click='isAddWord = true'
@click='wordFormMode = FormMode.Add'
/>
</div>
</div>
<WordList
v-if="runtimeStore.editDict.words.length"
<!-- <WordList-->
<!-- v-if="runtimeStore.editDict.originWords.length"-->
<!-- class="word-list"-->
<!-- :is-active="true"-->
<!-- @change="(i:number) => data.index = i"-->
<!-- :list="runtimeStore.editDict.originWords"-->
<!-- :activeIndex="data.index"/>-->
<WordList2
v-if="store.currentDict.words.length"
class="word-list"
:is-active="true"
@change="(i:number) => data.index = i"
:list="runtimeStore.editDict.words"
:activeIndex="data.index"/>
:list="store.currentDict.words"
:activeIndex="data.index">
<template v-slot="{word}">
<BaseIcon
v-if="!isWordCollect(word)"
class-name="collect"
@click="toggleWordCollect(word)"
title="收藏" icon="ph:star"/>
<BaseIcon
v-else
class-name="fill"
@click="toggleWordCollect(word)"
title="取消收藏" icon="ph:star-fill"/>
</template>
</WordList2>
<Empty v-else/>
</div>
</div>
<div class="edit" v-else>
<el-form
ref="ruleFormRef"
:rules="rules"
:model="form"
label-width="120px">
ref="dictFormRef"
:rules="dictRules"
:model="dictForm"
label-width="120rem">
<el-form-item label="名称" prop="name">
<el-input v-model="form.name"/>
<el-input v-model="dictForm.name"/>
</el-form-item>
<el-form-item label="描述">
<el-input v-model="form.description" type="textarea"/>
<el-input v-model="dictForm.description" type="textarea"/>
</el-form-item>
<el-form-item label="分类" prop="languageCategory">
<el-select
v-model="form.languageCategory" placeholder="请选择选项">
v-model="dictForm.languageCategory" placeholder="请选择选项">
<el-option :label="i.name" :value="i.id" v-for="i in languageCategoryOptions"/>
</el-select>
</el-form-item>
<el-form-item label="用途" prop="category">
<el-select v-model="form.category" placeholder="请选择选项">
<el-option :label="i" :value="i" v-for="i in categoryList[form.languageCategory]"/>
<el-select v-model="dictForm.category" placeholder="请选择选项">
<el-option :label="i" :value="i" v-for="i in categoryList[dictForm.languageCategory]"/>
</el-select>
</el-form-item>
<el-form-item label="标签" prop="tags">
<el-select
multiple
v-model="form.tags" placeholder="请选择选项">
<el-option :label="i" :value="i" v-for="i in tagList[form.category]"/>
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>
<div class="flex-center">
<el-button @click="step = 0">返回</el-button>
<el-button type="primary" @click="onSubmit">确定</el-button>
</el-form-item>
</div>
</el-form>
</div>
</div>
@@ -324,6 +390,7 @@ let isAddWord = $ref(false)
.more-info {
display: grid;
grid-template-columns: repeat(2, 1fr);
display: none;
}
}
@@ -404,7 +471,7 @@ let isAddWord = $ref(false)
}
}
.list {
.list-wrapper {
width: 350rem;
display: flex;
flex-direction: column;
@@ -427,6 +494,12 @@ let isAddWord = $ref(false)
}
}
}
.edit {
padding: 0 $space;
width: 100%;
box-sizing: border-box;
}
}
}
</style>

View File

@@ -12,7 +12,7 @@ import DictGroup from "@/components/Toolbar/DictGroup.vue";
import {v4 as uuidv4} from "uuid";
import {ActivityCalendar} from "vue-activity-calendar";
import "vue-activity-calendar/style.css";
import ChapterList from "@/components/ChapterList.vue";
import ChapterList from "@/components/list/ChapterList.vue";
import WordListModal from "@/components/Modal/WordListModal.vue";
import {isArticle} from "@/hooks/article.ts";
import {useRuntimeStore} from "@/stores/runtime.ts";

View File

@@ -6,7 +6,7 @@ import {$ref} from "vue/macros";
import {onMounted, onUnmounted, watch} from "vue";
import {usePlayWordAudio} from "@/hooks/sound.ts";
import {emitter, EventKey} from "@/utils/eventBus.ts";
import ListItem from "@/components/ListItem.vue";
import ListItem from "@/components/list/ListItem.vue";
import {Word} from "@/types.ts";
import {useRuntimeStore} from "@/stores/runtime.ts";

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import {useBaseStore} from "@/stores/base.ts"
import WordList from "@/components/WordList.vue"
import WordList from "@/components/list/WordList.vue"
import {$ref} from "vue/macros"
import {computed, provide, watch} from "vue"

View File

@@ -22,7 +22,7 @@ import Options from "@/components/Practice/Options.vue";
import Typing from "@/components/Practice/PracticeWord/Typing.vue";
import Panel from "@/components/Practice/Panel.vue";
import IconWrapper from "@/components/IconWrapper.vue";
import WordList from "@/components/WordList.vue";
import WordList from "@/components/list/WordList.vue";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {useWordOptions} from "@/hooks/dict.ts";
import {usePlayWordAudio} from "@/hooks/sound.ts";

View File

@@ -58,7 +58,7 @@ function showWordListModal(index: number, item: Word[]) {
</template>
<style scoped lang="scss">
@import "@/assets/css/variable.scss";
@import "@/assets/css/variable";
.list {
display: flex;

View File

@@ -0,0 +1,74 @@
<script setup lang="ts">
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
import {Word} from "@/types.ts";
import {usePlayWordAudio} from "@/hooks/sound.ts";
import {useWordOptions} from "@/hooks/dict.ts";
const props = withDefaults(defineProps<{
list?: Word[],
activeIndex?: number,
showDel?: boolean,
isActive?: boolean
active?: boolean
showTranslate?: boolean
showWord?: boolean
word: Word
}>(), {
activeIndex: -1,
isActive: false,
showDel: false,
showTranslate: true,
showWord: true
})
const emit = defineEmits<{
del: [val: Word],
change: [i: number]
}>()
const playWordAudio = usePlayWordAudio()
const {
isWordCollect,
toggleWordCollect,
isWordSimple,
toggleWordSimple,
} = useWordOptions()
</script>
<template>
<div class="common-list-item"
:class="{active}"
>
<div class="left">
<div class="item-title">
<span class="word" :class="!showWord && 'text-shadow'">{{ word.name }}</span>
<span class="phonetic">{{ word.usphone }}</span>
<VolumeIcon class="volume" @click="playWordAudio(word.name)"></VolumeIcon>
</div>
<div class="item-sub-title" v-if="word.trans.length && showTranslate">
<div v-for="item in word.trans">{{ item }}</div>
</div>
</div>
<div class="right">
<slot></slot>
</div>
</div>
</template>
<style scoped lang="scss">
.left {
display: flex;
gap: 10rem;
flex-direction: column;
word-break: break-word;
}
.right {
display: flex;
flex-direction: column;
gap: 5rem;
transition: all .3s;
}
</style>

View File

@@ -1,8 +1,8 @@
<script setup lang="ts">
import {Word} from "../types";
import {Word} from "../../types.ts";
import {watch} from "vue"
import {useSettingStore} from "@/stores/setting.ts";
import ListItem from "@/components/ListItem.vue";
import ListItem from "@/components/list/ListItem.vue";
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
import {usePlayWordAudio} from "@/hooks/sound.ts";
import {useWordOptions} from "@/hooks/dict.ts";
@@ -92,7 +92,7 @@ const {
<style scoped lang="scss">
@import "@/assets/css/variable.scss";
@import "@/assets/css/variable";
.list {
display: flex;

View File

@@ -0,0 +1,82 @@
<script setup lang="ts">
import {Word} from "../../types.ts";
import {useSettingStore} from "@/stores/setting.ts";
import WordItem from "@/components/list/WordItem.vue";
const props = withDefaults(defineProps<{
list: Word[],
activeIndex?: number,
showDel?: boolean,
isActive?: boolean
showTranslate?: boolean
showWord?: boolean
}>(), {
activeIndex: -1,
isActive: false,
showDel: false,
showTranslate: true,
showWord: true
})
const emit = defineEmits<{
del: [val: Word],
change: [i: number]
}>()
const settingStore = useSettingStore()
//
// const listRef: HTMLElement = $ref(null as any)
//
// function scrollViewToCenter(index: number) {
// if (index === -1) return
// listRef.children[index]?.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>
<virtual-list class="virtual-list"
:keeps="20"
data-key="name"
:data-sources="list"
:estimate-size="85"
item-class="dict-virtual-item"
>
<template #={source}>
<WordItem :word="source">
<slot :word="source"></slot>
</WordItem>
</template>
</virtual-list>
</template>
<style lang="scss">
@import "@/assets/css/variable";
.virtual-list {
overflow: overlay;
height: 100%;
padding: 0 $space;
}
.dict-virtual-item {
margin-bottom: 15rem;
}
</style>

View File

@@ -216,8 +216,8 @@ export const useBaseStore = defineStore('base', {
r.json().then(v => {
if (this.currentDict.translateLanguage === 'common') {
const runtimeStore = useRuntimeStore()
fetch('./translate/en2zh_CN.json').then(r => {
r.json().then((list: Word[]) => {
fetch('./translate/en2zh_CN.json').then(r2 => {
r2.json().then((list: Word[]) => {
runtimeStore.translateWordList = list
this.currentDict.originWords = cloneDeep(v)