Develop dictionary management function
This commit is contained in:
2
components.d.ts
vendored
2
components.d.ts
vendored
@@ -60,8 +60,10 @@ declare module 'vue' {
|
||||
Tooltip: typeof import('./src/components/Tooltip.vue')['default']
|
||||
TranslateSetting: typeof import('./src/components/toolbar/TranslateSetting.vue')['default']
|
||||
VirtualWordList: typeof import('./src/components/list/VirtualWordList.vue')['default']
|
||||
VirtualWordList2: typeof import('./src/components/list/VirtualWordList2.vue')['default']
|
||||
VolumeIcon: typeof import('./src/components/icon/VolumeIcon.vue')['default']
|
||||
VolumeSetting: typeof import('./src/components/toolbar/VolumeSetting.vue')['default']
|
||||
WordItem: typeof import('./src/components/list/WordItem.vue')['default']
|
||||
WordListDialog: typeof import('./src/components/dialog/WordListDialog.vue')['default']
|
||||
}
|
||||
export interface ComponentCustomProperties {
|
||||
|
||||
@@ -294,6 +294,10 @@ footer {
|
||||
padding: 0 var(--space);
|
||||
}
|
||||
|
||||
.list-item-wrapper{
|
||||
padding-bottom: 15rem;
|
||||
}
|
||||
|
||||
.common-list-item {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
@@ -310,9 +314,14 @@ footer {
|
||||
|
||||
.left {
|
||||
display: flex;
|
||||
gap: 3rem;
|
||||
flex-direction: column;
|
||||
word-break: break-word;
|
||||
gap: 10rem;
|
||||
|
||||
.title-wrapper{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3rem;
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
.right {
|
||||
@@ -326,7 +335,6 @@ footer {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
|
||||
&:hover {
|
||||
background: var(--color-item-hover);
|
||||
|
||||
|
||||
117
src/components/list/VirtualWordList2.vue
Normal file
117
src/components/list/VirtualWordList2.vue
Normal file
@@ -0,0 +1,117 @@
|
||||
<script setup lang="ts">
|
||||
import {Word} from "../../types.ts";
|
||||
import {useSettingStore} from "@/stores/setting.ts";
|
||||
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
|
||||
import {usePlayWordAudio} from "@/hooks/sound.ts";
|
||||
import {watch} from 'vue'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
list: Word[],
|
||||
activeIndex?: number,
|
||||
isActive?: boolean
|
||||
showTranslate?: boolean
|
||||
showWord?: boolean
|
||||
}>(), {
|
||||
activeIndex: -1,
|
||||
isActive: false,
|
||||
showTranslate: true,
|
||||
showWord: true
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
click: [val: { word: Word, index: number }],
|
||||
}>()
|
||||
|
||||
const settingStore = useSettingStore()
|
||||
const listRef: any = $ref()
|
||||
|
||||
function scrollViewToCenter(index: number) {
|
||||
if (index === -1) return
|
||||
listRef.scrollToIndex(index)
|
||||
// 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)
|
||||
// })
|
||||
|
||||
const playWordAudio = usePlayWordAudio()
|
||||
|
||||
function reset() {
|
||||
listRef.reset()
|
||||
}
|
||||
|
||||
function scrollToBottom() {
|
||||
listRef.scrollToIndex(props.list.length - 1)
|
||||
}
|
||||
|
||||
defineExpose({scrollToBottom})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DynamicScroller
|
||||
:items="list"
|
||||
ref="listRef"
|
||||
:min-item-size="90"
|
||||
class="scroller"
|
||||
>
|
||||
<template v-slot="{ item, index, active }">
|
||||
<DynamicScrollerItem
|
||||
:item="item"
|
||||
:active="active"
|
||||
:size-dependencies="[
|
||||
item.id,
|
||||
]"
|
||||
:data-index="index"
|
||||
>
|
||||
<div class="list-item-wrapper">
|
||||
<div class="common-list-item"
|
||||
:class="{active:activeIndex === index}"
|
||||
@click="emit('click',{word:item,index})"
|
||||
>
|
||||
<div class="left">
|
||||
<slot name="prefix" :word="item" :index="index"></slot>
|
||||
<div class="title-wrapper">
|
||||
<div class="item-title">
|
||||
<span class="word" :class="!showWord && 'text-shadow'">{{ item.name }}</span>
|
||||
<span class="phonetic">{{ item.usphone }}</span>
|
||||
<VolumeIcon class="volume" @click="playWordAudio(item.name)"></VolumeIcon>
|
||||
</div>
|
||||
<div class="item-sub-title" v-if="item.trans.length && showTranslate">
|
||||
<div v-for="item in item.trans">{{ item }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<slot :word="item" :index="index"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DynamicScrollerItem>
|
||||
</template>
|
||||
</DynamicScroller>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "@/assets/css/variable";
|
||||
|
||||
.scroller {
|
||||
height: 100%;
|
||||
padding: 0 var(--space);
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
11
src/components/list/WordItem.vue
Normal file
11
src/components/list/WordItem.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
@@ -8,6 +8,8 @@ import VirtualList from 'vue-virtual-list-v3';
|
||||
import ZH from "@/locales/zh-CN.ts";
|
||||
import {createI18n} from 'vue-i18n'
|
||||
import router from "@/router.ts";
|
||||
import VueVirtualScroller from 'vue-virtual-scroller'
|
||||
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
|
||||
|
||||
const i18n = createI18n({
|
||||
locale: 'zh-CN',
|
||||
@@ -21,6 +23,7 @@ const pinia = createPinia()
|
||||
// const app = createApp(Mobile)
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(VueVirtualScroller)
|
||||
// app.use(ElementPlus)
|
||||
app.use(pinia)
|
||||
app.use(VirtualList);
|
||||
|
||||
@@ -27,6 +27,8 @@ import VirtualWordList from "@/components/list/VirtualWordList.vue";
|
||||
import Dialog from "@/components/dialog/Dialog.vue";
|
||||
import {nanoid} from "nanoid";
|
||||
import {no} from "@/utils";
|
||||
import Test from "@/pages/dict/Test.vue";
|
||||
import VirtualWordList2 from "@/components/list/VirtualWordList2.vue";
|
||||
|
||||
const store = useBaseStore()
|
||||
const settingStore = useSettingStore()
|
||||
@@ -80,7 +82,9 @@ async function selectDict(val: {
|
||||
if (!runtimeStore.editDict.originWords.length) {
|
||||
let r = await fetch(url)
|
||||
let v = await r.json()
|
||||
v.map(s => s.id = nanoid(6))
|
||||
v.map(s => {
|
||||
s.id = nanoid(6)
|
||||
})
|
||||
runtimeStore.editDict.originWords = cloneDeep(v)
|
||||
changeSort(runtimeStore.editDict.sort)
|
||||
}
|
||||
@@ -550,8 +554,13 @@ function resetChapterList() {
|
||||
chapterList2 = Array.from({length: runtimeStore.editDict.chapterWords.length}).map((v, i) => ({id: i}))
|
||||
}
|
||||
|
||||
function handleCheckedChapterWordListChange(source: any) {
|
||||
source.checked = !source.checked
|
||||
function handleCheckedChapterWordListChange({word: source}: any) {
|
||||
// source.checked = !source.checked
|
||||
let rIndex = currentChapterWordList.findIndex(v => v.id === source.id)
|
||||
console.log('handleCheckedChapterWordListChange', currentChapterWordList[rIndex].checked)
|
||||
if (rIndex > -1) {
|
||||
currentChapterWordList[rIndex].checked = !currentChapterWordList[rIndex].checked
|
||||
}
|
||||
currentChapterWordListCheckAll = currentChapterWordList.every(v => v.checked)
|
||||
if (currentChapterWordListCheckAll) {
|
||||
currentChapterWordListIsIndeterminate = false
|
||||
@@ -657,7 +666,7 @@ const isPinDict = $computed(() => {
|
||||
</div>
|
||||
<BaseButton size="small" @click="exportData">导出</BaseButton>
|
||||
</div>
|
||||
<div class="desc" v-if="runtimeStore.editDict.description">{{runtimeStore.editDict.description}}</div>
|
||||
<div class="desc" v-if="runtimeStore.editDict.description">{{ runtimeStore.editDict.description }}</div>
|
||||
<div class="num">总词汇: {{ runtimeStore.editDict.originWords.length }}词</div>
|
||||
</div>
|
||||
</header>
|
||||
@@ -742,48 +751,72 @@ const isPinDict = $computed(() => {
|
||||
</div>
|
||||
</div>
|
||||
<div class="wrapper">
|
||||
<virtual-list class="virtual-list"
|
||||
v-loading="loading"
|
||||
v-if="currentChapterWordList.length"
|
||||
:keeps="20"
|
||||
data-key="id"
|
||||
:data-sources="currentChapterWordList"
|
||||
:estimate-size="45"
|
||||
>
|
||||
<template #={source,index}>
|
||||
<div class="common-list-item space15"
|
||||
@click="handleCheckedChapterWordListChange(source)">
|
||||
<div class="flex gap10">
|
||||
<el-checkbox v-model="source.checked"
|
||||
@change="handleCheckedChapterWordListChange(source)"
|
||||
size="large"/>
|
||||
<div class="left">
|
||||
<div class="item-title">
|
||||
<span class="word">{{ 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">
|
||||
<div v-for="item in source.trans">{{ item }}</div>
|
||||
<VirtualWordList2
|
||||
:list="currentChapterWordList"
|
||||
@click="handleCheckedChapterWordListChange"
|
||||
>
|
||||
<template v-slot:prefix="{word}">
|
||||
<el-checkbox v-model="word.checked"
|
||||
@change="handleCheckedChapterWordListChange({word})"
|
||||
size="large"/>
|
||||
</template>
|
||||
<template v-slot="{word,index}">
|
||||
<BaseIcon
|
||||
class-name="del"
|
||||
@click="editWord(word,index,'chapter')"
|
||||
title="编辑"
|
||||
icon="tabler:edit"/>
|
||||
<BaseIcon
|
||||
class-name="del"
|
||||
@click="delWord(word,index,'chapter')"
|
||||
title="移除"
|
||||
icon="solar:trash-bin-minimalistic-linear"/>
|
||||
</template>
|
||||
</VirtualWordList2>
|
||||
<template v-if="false">
|
||||
<virtual-list class="virtual-list"
|
||||
v-loading="loading"
|
||||
v-if="currentChapterWordList.length"
|
||||
:keeps="20"
|
||||
data-key="id"
|
||||
:data-sources="currentChapterWordList"
|
||||
:estimate-size="45"
|
||||
>
|
||||
<template #={source,index}>
|
||||
<div class="common-list-item space15"
|
||||
@click="handleCheckedChapterWordListChange(source)">
|
||||
<div class="flex gap10">
|
||||
<el-checkbox v-model="source.checked"
|
||||
@change="handleCheckedChapterWordListChange(source)"
|
||||
size="large"/>
|
||||
<div class="left">
|
||||
<div class="item-title">
|
||||
<span class="word">{{ 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">
|
||||
<div v-for="item in source.trans">{{ item }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<BaseIcon
|
||||
class-name="del"
|
||||
@click="editWord(source,index,'chapter')"
|
||||
title="编辑"
|
||||
icon="tabler:edit"/>
|
||||
<BaseIcon
|
||||
class-name="del"
|
||||
@click="delWord(source,index,'chapter')"
|
||||
title="移除"
|
||||
icon="solar:trash-bin-minimalistic-linear"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<BaseIcon
|
||||
class-name="del"
|
||||
@click="editWord(source,index,'chapter')"
|
||||
title="编辑"
|
||||
icon="tabler:edit"/>
|
||||
<BaseIcon
|
||||
class-name="del"
|
||||
@click="delWord(source,index,'chapter')"
|
||||
title="移除"
|
||||
icon="solar:trash-bin-minimalistic-linear"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</virtual-list>
|
||||
<Empty text="请选择章节" v-else/>
|
||||
</template>
|
||||
</virtual-list>
|
||||
<Empty text="请选择章节" v-else/>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="options-column">
|
||||
|
||||
35
src/pages/dict/Test.vue
Normal file
35
src/pages/dict/Test.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<script setup lang="ts">
|
||||
import {Word} from "@/types.ts";
|
||||
|
||||
const props = defineProps<{
|
||||
items: Word[]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DynamicScroller
|
||||
:items="items"
|
||||
:min-item-size="54"
|
||||
class="scroller"
|
||||
>
|
||||
<template v-slot="{ item, index, active }">
|
||||
<DynamicScrollerItem
|
||||
:item="item"
|
||||
:active="active"
|
||||
:size-dependencies="[
|
||||
item.name,
|
||||
]"
|
||||
:data-index="index"
|
||||
>
|
||||
|
||||
<div class="text">{{ item.name }}</div>
|
||||
</DynamicScrollerItem>
|
||||
</template>
|
||||
</DynamicScroller>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.scroller {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -220,7 +220,9 @@ export const useBaseStore = defineStore('base', {
|
||||
let r = await fetch(dictResourceUrl)
|
||||
// let r = await fetch(`.${this.currentDict.url}`)
|
||||
let v = await r.json()
|
||||
v.map(s => s.id = nanoid(6))
|
||||
v.map(s => {
|
||||
s.id = nanoid(6)
|
||||
})
|
||||
if (this.currentDict.translateLanguage === 'common') {
|
||||
const runtimeStore = useRuntimeStore()
|
||||
let r2 = await fetch('./translate/en2zh_CN-min.json')
|
||||
|
||||
Reference in New Issue
Block a user