remove surplus list

This commit is contained in:
zyronon
2023-12-01 23:49:11 +08:00
parent bdd3b501a0
commit be4912cb40
23 changed files with 99 additions and 999 deletions

8
components.d.ts vendored
View File

@@ -8,17 +8,13 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
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']
ArticleList4: typeof import('./src/components/list2/ArticleList4.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']
BaseList: typeof import('./src/components/list2/BaseList.vue')['default']
ChapterList: typeof import('./src/components/list/ChapterList.vue')['default']
ChapterName: typeof import('./src/components/toolbar/ChapterName.vue')['default']
Close: typeof import('./src/components/icon/Close.vue')['default']
CommonWordList: typeof import('./src/components/list/CommonWordList.vue')['default']
Dialog: typeof import('./src/components/dialog/Dialog.vue')['default']
DictDiglog: typeof import('./src/components/dialog/DictDiglog.vue')['default']
DictGroup: typeof import('./src/components/toolbar/DictGroup.vue')['default']
@@ -49,7 +45,6 @@ declare module 'vue' {
IconWrapper: typeof import('./src/components/IconWrapper.vue')['default']
Input: typeof import('./src/components/Input.vue')['default']
List: typeof import('./src/components/list/List.vue')['default']
ListItem: typeof import('./src/components/list/ListItem.vue')['default']
MiniDialog: typeof import('./src/components/dialog/MiniDialog.vue')['default']
PopConfirm: typeof import('./src/components/PopConfirm.vue')['default']
RepeatSetting: typeof import('./src/components/toolbar/RepeatSetting.vue')['default']
@@ -61,11 +56,8 @@ declare module 'vue' {
Toolbar: typeof import('./src/components/toolbar/index.vue')['default']
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']
WordList: typeof import('./src/components/list2/WordList.vue')['default']
WordListDialog: typeof import('./src/components/dialog/WordListDialog.vue')['default']
}

View File

@@ -7,7 +7,6 @@ import {Icon} from "@iconify/vue";
defineProps<{
title?: string,
icon: string,
className?: string
}>()
defineEmits(['click'])
@@ -15,7 +14,7 @@ defineEmits(['click'])
<template>
<Tooltip :title="title">
<IconWrapper :class="className">
<IconWrapper v-bind="$attrs">
<Icon @click.stop="$emit('click')" :icon="icon"/>
</IconWrapper>
</Tooltip>

View File

@@ -52,28 +52,40 @@ async function selectDict(val: { dict: DictResource | Dict, index: number }) {
...cloneDeep(DefaultDict),
...item,
})
runtimeStore.editDict.id = nanoid(6)
//设置默认章节单词数
runtimeStore.editDict.chapterWordNumber = settingStore.chapterWordNumber
}
if ([DictType.collect, DictType.simple, DictType.wrong].includes(runtimeStore.editDict.type)) {
} else {
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)
changeSort(runtimeStore.editDict.sort)
//如果不是自定义词典并且有url地址才去下载
if (!runtimeStore.editDict.isCustom && runtimeStore.editDict.url) {
let url = `./dicts/${runtimeStore.editDict.language}/${runtimeStore.editDict.type}/${runtimeStore.editDict.translateLanguage}/${runtimeStore.editDict.url}`;
if (runtimeStore.editDict.type === DictType.word) {
if (!runtimeStore.editDict.originWords.length) {
let r = await fetch(url)
let v = await r.json()
v.map(s => {
s.id = nanoid(6)
})
runtimeStore.editDict.originWords = cloneDeep(v)
changeSort(runtimeStore.editDict.sort)
} else {
runtimeStore.editDict.length = runtimeStore.editDict.words.length + runtimeStore.editDict.residueWords.length
}
}
}
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 = nanoid(6)
return s
}))
if (runtimeStore.editDict.type === DictType.article) {
if (!runtimeStore.editDict.articles.length) {
let r = await fetch(url)
let v = await r.json()
v.map(s => {
s.id = nanoid(6)
})
runtimeStore.editDict.articles = cloneDeep(v)
} else {
runtimeStore.editDict.length = runtimeStore.editDict.articles.length
}
}
}
}
@@ -121,24 +133,13 @@ function changeSort(v) {
resetChapterList()
}
function editDict() {
show = false
setTimeout(() => {
router.push({path: '/dict', query: {type: 'editDict'}})
}, 500)
}
let wordListRef: any = $ref()
function add() {
function option(type: string) {
show = false
setTimeout(() => {
router.push({path: '/dict', query: {type: 'addWordOrArticle'}})
router.push({path: '/dict', query: {type: type}})
}, 500)
if (dictIsArticle) {
} else {
}
}
/**/
@@ -215,9 +216,10 @@ function showWordListModal(val: { item: Word, index: number }) {
<div class="left-column">
<BaseIcon
v-if="![DictType.collect,DictType.wrong,DictType.simple].includes(runtimeStore.editDict.type)"
class-name="edit-icon"
class="edit-icon"
title="编辑词典"
icon="tabler:edit"
@click='editDict'
@click='option("editDict")'
/>
<div class="name">{{ runtimeStore.editDict.name }}</div>
<div class="desc">{{ runtimeStore.editDict.description }}</div>
@@ -230,7 +232,8 @@ function showWordListModal(val: { item: Word, index: number }) {
}}</span>
</div>
<BaseIcon icon="mi:add"
@click='add'
@click='option("addWordOrArticle")'
:title="`添加${dictIsArticle?'文章':'单词'}`"
/>
</div>
<div class="text">开始日期-</div>
@@ -322,7 +325,15 @@ function showWordListModal(val: { item: Word, index: number }) {
</div>
</div>
<div class="right-column">
<div class="common-title">{{ dictIsArticle ? '文章' : '章节' }}列表</div>
<div class="common-title">
<span>{{ dictIsArticle ? '文章' : '章节' }}列表</span>
<BaseIcon
icon="fluent:notepad-edit-20-regular"
@click='option("detail")'
style="position: absolute;right: 20rem;"
:title="`管理${dictIsArticle?'文章':'章节'}`"
/>
</div>
<template v-if="dictIsArticle">
<ArticleList4
v-if="runtimeStore.editDict.articles.length"
@@ -457,13 +468,13 @@ $header-height: 60rem;
padding-right: var(--space);
.name {
font-size: 28rem;
margin-bottom: 10rem;
font-size: 24rem;
width: 95%;
}
.desc {
font-size: 18rem;
margin-bottom: 30rem;
font-size: 16rem;
margin-bottom: 20rem;
}
.count {
@@ -471,13 +482,9 @@ $header-height: 60rem;
border-bottom: 2px solid var(--color-item-active);
}
.edit-icon {
}
:deep(.edit-icon) {
position: absolute;
top: 8rem;
top: 0;
right: 0;
}
}

View File

@@ -5,7 +5,7 @@ import {$ref} from "vue/macros";
import {onMounted, onUnmounted, watch} from "vue";
import {emitter, EventKey} from "@/utils/eventBus.ts";
import {useRuntimeStore} from "@/stores/runtime.ts";
import VirtualWordList from "@/components/list/VirtualWordList.vue";
import WordList from "@/components/list2/WordList.vue";
let show = $ref(false)
let loading = $ref(false)
@@ -18,7 +18,7 @@ onMounted(() => {
emitter.on(EventKey.openWordListModal, (val: any) => {
show = true
list = val.list
title = val.title
title = val.title + `(${list.length}词)`
requestIdleCallback(() => {
let count = 0
if (val.translateLanguage === 'common') {
@@ -77,10 +77,10 @@ onUnmounted(() => {
:indeterminate="false"
:show-text="false"/>
</div>
<VirtualWordList
<WordList
class="word-list"
:list="list">
</VirtualWordList>
</WordList>
</div>
</Dialog>
</template>
@@ -93,6 +93,8 @@ onUnmounted(() => {
padding-top: 0;
width: 400rem;
height: 75vh;
display: flex;
flex-direction: column;
.progress-wrapper {
padding: 0 var(--space);

View File

@@ -1,122 +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";
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
const props = withDefaults(defineProps<{
list: Article[],
activeIndex?: number,
isActive?: boolean
showTranslate?: 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>
<div class="common-list-item"
v-for="(source,index) in localList" :key="index"
:class="{active:activeIndex === index}"
@click="emit('selectItem',source)"
>
<div class="left">
<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">
<slot :source="source" :index="index"></slot>
</div>
</div>
</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 {
box-sizing: border-box;
width: 100%;
}
.translate {
font-size: 16rem;
}
}
</style>

View File

@@ -1,158 +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";
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
const props = withDefaults(defineProps<{
list: Article[],
activeIndex?: number,
isActive?: boolean
showTranslate?: boolean
}>(), {
list: [],
activeIndex: -1,
isActive: false,
showTranslate:true
})
const emit = defineEmits<{
click: [val: { data: Article, index: number }],
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)
})
function scrollToBottom() {
listRef.scrollToBottom()
}
function scrollToItem(index: number) {
listRef.scrollToItem(index)
}
defineExpose({scrollToBottom, scrollToItem})
</script>
<template>
<div class="list">
<div class="search">
<Input v-model="searchKey"/>
</div>
<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',{data:item,index})"
>
<div class="left">
<slot name="prefix" :data="item" :index="index"></slot>
<div class="title-wrapper">
<div class="item-title">
<div class="name"> {{ `${index + 1}. ${item.title}` }}</div>
</div>
<div class="item-sub-title" v-if="item.titleTranslate && showTranslate">
<div class="item-translate"> {{ ` ${item.titleTranslate}` }}</div>
</div>
</div>
</div>
<div class="right">
<slot :data="item" :index="index"></slot>
</div>
</div>
</div>
</DynamicScrollerItem>
</template>
</DynamicScroller>
</div>
</template>
<style scoped lang="scss">
@import "@/assets/css/variable.scss";
.list {
display: flex;
flex-direction: column;
gap: 15rem;
flex: 1;
height: 100%;
.search {
box-sizing: border-box;
width: 100%;
padding: 0 var(--space);
}
.translate {
font-size: 16rem;
}
.scroller {
flex: 1;
padding: 0 var(--space);
}
}
</style>

View File

@@ -1,108 +0,0 @@
<script setup lang="ts">
import {Dict, Word} from "@/types.ts"
import {emitter, EventKey} from "@/utils/eventBus.ts";
import {$computed} from "vue/macros";
import {useRuntimeStore} from "@/stores/runtime.ts";
import BaseIcon from "@/components/BaseIcon.vue";
const props = defineProps<{
dict: Dict,
activeIndex?: number
isArticle?: boolean
}>()
const emit = defineEmits<{
'update:activeIndex': [index: number]
}>()
const runtimeStore = useRuntimeStore()
const list: any[] = $computed(() => {
if (props.isArticle) return props.dict.articles
return props.dict.chapterWords
})
function showWordListModal(index: number, item: Word[]) {
emitter.emit(EventKey.openWordListModal, {
title: `${index + 1}`,
translateLanguage: runtimeStore.editDict.translateLanguage,
list: item
})
}
</script>
<template>
<div class="list">
<div class="common-list-item"
:class="activeIndex === index && 'active'"
v-for="(item,index) in list"
@click="emit('update:activeIndex', index)">
<div class="flex gap10">
<input type="radio" :checked="activeIndex === index">
<div class="left">
<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>
</div>
</template>
<style scoped lang="scss">
@import "@/assets/css/variable";
.list {
display: flex;
flex-direction: column;
width: 100%;
flex: 1;
overflow: auto;
box-sizing: border-box;
gap: 10rem;
padding: 0 var(--space);
//padding-right: 10rem;
.common-list-item {
input {
cursor: pointer;
}
.item-title {
transition: all .3s;
cursor: pointer;
border-bottom: 2px solid transparent;
}
:deep(.del) {
opacity: 0;
}
&:hover {
.item-title {
border-bottom: 2px solid gray !important;
}
:deep(.del) {
opacity: 1;
}
}
}
}
</style>

View File

@@ -1,91 +0,0 @@
<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<{
change: [val: { word: Word, index: 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)
})
const playWordAudio = usePlayWordAudio()
</script>
<template>
<div class="common-list" ref="listRef">
<div class="common-list-item"
v-for="(source,index) in list" :key="index"
:class="{active:activeIndex === index}"
@click="emit('change',{word:source,index})"
>
<div class="left">
<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">
<slot :word="source" :index="index"></slot>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
@import "@/assets/css/variable";
.common-list {
display: flex;
flex-direction: column;
gap: 15rem;
flex: 1;
overflow: overlay;
padding: 0 var(--space);
}
</style>

View File

@@ -1,126 +0,0 @@
<script setup lang="ts">
import BaseIcon from "@/components/BaseIcon.vue";
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
defineProps<{
showVolume?: boolean,
showDel?: boolean,
active?: boolean
isCollect?: boolean
isSimple?: boolean
}>()
defineEmits<{
toggleSimple: [],
toggleCollect: [],
del: [],
}>()
</script>
<template>
<div class="list-item"
:class="{active}"
>
<div class="left">
<slot></slot>
</div>
<div class="right">
<BaseIcon
v-if="showDel"
class-name="del"
@click="$emit('del')"
title="移除"
icon="solar:trash-bin-minimalistic-linear"/>
<template v-else>
<BaseIcon
v-if="!isCollect"
class-name="collect"
@click="$emit('toggleCollect')"
title="收藏" icon="ph:star"/>
<BaseIcon
v-else
class-name="fill"
@click="$emit('toggleCollect')"
title="取消收藏" icon="ph:star-fill"/>
<BaseIcon
v-if="!isSimple"
class-name="easy"
@click="$emit('toggleSimple')"
title="标记为简单词"
icon="material-symbols:check-circle-outline-rounded"/>
<BaseIcon
v-else
class-name="fill"
@click="$emit('toggleSimple')"
title="取消标记简单词"
icon="material-symbols:check-circle-rounded"/>
</template>
</div>
</div>
</template>
<style scoped lang="scss">
.list-item {
.left {
display: flex;
gap: 10rem;
flex-direction: column;
word-break: break-word;
}
.right {
display: flex;
flex-direction: column;
gap: 5rem;
transition: all .3s;
}
:deep(.collect) {
opacity: 0;
}
:deep(.easy) {
opacity: 0;
}
&:hover {
:deep(.collect) {
opacity: 1;
}
:deep(.easy) {
opacity: 1;
}
}
&.active {
:deep(.sub-title) {
//color: black;
}
:deep(.title) {
//color: black;
}
$c: #E6A23C;
:deep(.collect) {
color: $c;
}
:deep(.fill) {
color: $c;
}
:deep(.easy) {
color: $c;
}
}
}
</style>

View File

@@ -1,108 +0,0 @@
<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<{
change: [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>
<virtual-list class="virtual-list"
:keeps="20"
data-key="name"
:data-sources="list"
:estimate-size="85"
ref="listRef"
>
<template #={source,index}>
<div class="common-list-item space15"
:class="{active:activeIndex === index}"
@click="emit('change',{word:source,index})"
>
<div class="left">
<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">
<slot :word="source" :index="index"></slot>
</div>
</div>
</template>
</virtual-list>
</template>
<style lang="scss" scoped>
@import "@/assets/css/variable";
.virtual-list {
overflow: overlay;
height: 100%;
padding: 0 var(--space);
}
</style>

View File

@@ -1,121 +0,0 @@
<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.scrollToBottom()
}
function scrollToItem(index: number) {
listRef.scrollToItem(index)
}
defineExpose({scrollToBottom, scrollToItem})
</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="tran in item.trans">{{ tran }}</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>

View File

@@ -1,11 +0,0 @@
<script setup lang="ts">
</script>
<template>
</template>
<style scoped lang="scss">
</style>

View File

@@ -2,11 +2,7 @@
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 {onMounted, useAttrs, watch} from "vue";
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
import {Article} from "@/types.ts";
import BaseList from "@/components/list2/BaseList.vue";
const props = withDefaults(defineProps<{

View File

@@ -76,6 +76,11 @@ onMounted(() => {
})
}, 300)
break
case 'detail':
setTimeout(() => {
selectDict({dict: runtimeStore.editDict})
}, 300)
break
case 'editDict':
setTimeout(() => {
selectDict({dict: runtimeStore.editDict}, () => {
@@ -97,12 +102,10 @@ onMounted(() => {
step = 0
}
if (type === "collect") {
selectDict({dict: store.collect, index: 0})
// addWord('residue')
selectDict({dict: store.collect})
}
if (type === "simple") {
selectDict({dict: store.simple, index: 0})
// addWord('residue')
selectDict({dict: store.simple})
}
})
// console.log('categoryList', categoryList)

View File

@@ -1,35 +0,0 @@
<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>

View File

@@ -4,7 +4,6 @@ import BaseIcon from "@/components/BaseIcon.vue";
import BaseButton from "@/components/BaseButton.vue";
import Empty from "@/components/Empty.vue";
import {useRuntimeStore} from "@/stores/runtime.ts";
import ArticleList3 from "@/components/list/ArticleList3.vue";
import {$computed, $ref} from "vue/macros";
import {cloneDeep} from "lodash-es";
import {Article, DefaultArticle, DefaultDict, Dict, DictResource, DictType, Sort, TranslateType} from "@/types.ts";
@@ -95,9 +94,10 @@ async function getDictDetail(val: {
s.id = nanoid(6)
})
runtimeStore.editDict.articles = cloneDeep(v)
} else {
runtimeStore.editDict.length = runtimeStore.editDict.articles.length
}
}
runtimeStore.editDict.length = runtimeStore.editDict.articles.length + runtimeStore.editDict
loading = false
}
@@ -322,12 +322,12 @@ defineExpose({getDictDetail, add, editDict})
</template>
<template v-slot:suffix="{item,index}">
<BaseIcon
class-name="del"
class="del"
@click="emitter.emit(EventKey.openArticleListModal,item)"
title="编辑"
icon="tabler:edit"/>
<BaseIcon
class-name="del"
class="del"
@click="delArticle(index)"
title="删除"
icon="solar:trash-bin-minimalistic-linear"/>

View File

@@ -1,7 +1,6 @@
<script setup lang="ts">
import {Sort, Word} from "@/types.ts";
import VirtualWordList2 from "@/components/list/VirtualWordList2.vue";
import BaseIcon from "@/components/BaseIcon.vue";
import Empty from "@/components/Empty.vue";
import {$computed, $ref} from "vue/macros";
@@ -10,9 +9,8 @@ import MiniDialog from "@/components/dialog/MiniDialog.vue";
import BaseButton from "@/components/BaseButton.vue";
import {useWindowClick} from "@/hooks/event.ts";
import {reverse, shuffle} from "lodash-es";
import BaseList from "@/components/list2/BaseList.vue";
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
import {usePlayWordAudio} from "@/hooks/sound.ts";
import WordList from '@/components/list2/WordList.vue'
const props = defineProps<{
title: string,
@@ -24,8 +22,8 @@ const props = defineProps<{
const emit = defineEmits<{
add: []
edit: [val: { word: Word, index: number }]
del: [val: { word: Word, index: number }],
edit: [val: { item: Word, index: number }]
del: [val: { item: Word, index: number }],
'update:list': [val: Word[]]
}>()
@@ -61,7 +59,7 @@ let checkedTotal = $computed(() => {
return props.list.filter(v => v.checked).length
})
function del(val: { word: Word, index: number }) {
function del(val: { item: Word, index: number }) {
props.list.splice(val.index, 1)
emit('del', val)
}
@@ -144,8 +142,7 @@ const playWordAudio = usePlayWordAudio()
</div>
</div>
<div class="wrapper">
<!-- TODO -->
<BaseList
<WordList
ref="listRef"
:list="list"
v-if="list.length"
@@ -156,29 +153,19 @@ const playWordAudio = usePlayWordAudio()
@change="handleCheckedChange({item})"
size="large"/>
</template>
<template v-slot="{item,index}">
<div class="item-title">
<span class="word">{{ 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">
<div v-for="tran in item.trans">{{ tran }}</div>
</div>
</template>
<template v-slot:suffix="{item,index}" v-if="canOperation">
<BaseIcon
class-name="del"
class="del"
@click="emit('edit',{item,index})"
title="编辑"
icon="tabler:edit"/>
<BaseIcon
class-name="del"
class="del"
@click="del({item,index})"
title="删除"
icon="solar:trash-bin-minimalistic-linear"/>
</template>
</BaseList>
</WordList>
<Empty :text="emptyTitle" v-else/>
</div>
</div>

View File

@@ -604,7 +604,7 @@ defineExpose({getDictDetail, add: addWord, editDict})
</template>
<template v-slot:suffix="{ item, index }" v-if="isCanOperation">
<BaseIcon
class-name="del"
class="del"
@click="delWordChapter(item.id)"
title="移除"
icon="solar:trash-bin-minimalistic-linear"/>

View File

@@ -29,26 +29,26 @@ const settingStore = useSettingStore()
<div class="options">
<BaseIcon
v-if="!isSimple"
class-name="collect"
class="collect"
@click="$emit('toggleSimple')"
:title="`标记为简单词(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.ToggleSimple]})`"
icon="material-symbols:check-circle-outline-rounded"/>
<BaseIcon
v-else
class-name="fill"
class="fill"
@click="$emit('toggleSimple')"
:title="`取消标记简单词(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.ToggleSimple]})`"
icon="material-symbols:check-circle-rounded"/>
<BaseIcon
v-if="!isCollect"
class-name="collect"
class="collect"
@click="$emit('toggleCollect')"
:title="`收藏(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.ToggleCollect]})`"
icon="ph:star"/>
<BaseIcon
v-else
class-name="fill"
class="fill"
@click="$emit('toggleCollect')"
:title="`取消收藏(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.ToggleCollect]})`"
icon="ph:star-fill"/>

View File

@@ -13,14 +13,13 @@ import {useArticleOptions, useWordOptions} from "@/hooks/dict.ts";
import {Icon} from "@iconify/vue";
import Tooltip from "@/components/Tooltip.vue";
import IconWrapper from "@/components/IconWrapper.vue";
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";
import WordList from "@/components/list2/WordList.vue";
import ArticleList4 from "@/components/list2/ArticleList4.vue";
const router = useRouter()
const store = useBaseStore()
@@ -133,7 +132,7 @@ function addSimple() {
:list="store.collect.words">
<template v-slot:suffix="{item,index}">
<BaseIcon
class-name="del"
class="del"
@click="toggleWordCollect(item)"
title="移除"
icon="solar:trash-bin-minimalistic-linear"/>
@@ -142,19 +141,17 @@ function addSimple() {
<Empty v-else/>
</template>
<template v-else>
<ArticleList2
<ArticleList4
v-if="store.collect.articles.length"
:show-translate="true"
v-model:list="store.collect.articles">
<template v-slot="{source,index}">
<template v-slot:suffix="{item,index}">
<BaseIcon
class-name="del"
@click="toggleArticleCollect(source)"
class="del"
@click="toggleArticleCollect(item)"
title="移除"
icon="solar:trash-bin-minimalistic-linear"/>
</template>
</ArticleList2>
</ArticleList4>
<Empty v-else/>
</template>
</div>
@@ -185,7 +182,7 @@ function addSimple() {
:list="store.simple.words">
<template v-slot:suffix="{item,index}">
<BaseIcon
class-name="del"
class="del"
@click="delSimpleWord(item)"
title="移除"
icon="solar:trash-bin-minimalistic-linear"/>
@@ -213,7 +210,7 @@ function addSimple() {
:list="store.wrong.words">
<template v-slot="{item,index}">
<BaseIcon
class-name="del"
class="del"
@click="delWrongWord(item)"
title="移除"
icon="solar:trash-bin-minimalistic-linear"/>

View File

@@ -393,13 +393,13 @@ const {
/>
<BaseIcon
v-if="!isArticleCollect(props.article)"
class-name="collect"
class="collect"
@click="toggleArticleCollect(props.article)"
:title="`收藏(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.ToggleCollect]})`"
icon="ph:star"/>
<BaseIcon
v-else
class-name="fill"
class="fill"
@click="toggleArticleCollect(props.article)"
:title="`取消收藏(快捷键:${settingStore.shortcutKeyMap[ShortcutKey.ToggleCollect]})`"
icon="ph:star-fill"/>

View File

@@ -17,11 +17,9 @@ import {Icon} from "@iconify/vue";
import Tooltip from "@/components/Tooltip.vue";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {useSettingStore} from "@/stores/setting.ts";
import ArticleList2 from "@/components/list/ArticleList2.vue";
import BaseIcon from "@/components/BaseIcon.vue";
import {useArticleOptions} from "@/hooks/dict.ts";
import ArticleList4 from "@/components/list2/ArticleList4.vue";
import WordList from "@/components/list2/WordList.vue";
const store = useBaseStore()
const practiceStore = usePracticeStore()
@@ -290,12 +288,12 @@ const {
<template v-slot:suffix="{item,index}">
<BaseIcon
v-if="!isArticleCollect(item)"
class-name="collect"
class="collect"
@click="toggleArticleCollect(item)"
title="收藏" icon="ph:star"/>
<BaseIcon
v-else
class-name="fill"
class="fill"
@click="toggleArticleCollect(item)"
title="取消收藏" icon="ph:star-fill"/>
</template>

View File

@@ -17,7 +17,6 @@ import IconWrapper from "@/components/IconWrapper.vue";
import {useRuntimeStore} from "@/stores/runtime.ts";
import {useWordOptions} from "@/hooks/dict.ts";
import BaseIcon from "@/components/BaseIcon.vue";
import CommonWordList from "@/components/list/CommonWordList.vue";
import WordList from "@/components/list2/WordList.vue";
import Empty from "@/components/Empty.vue";
@@ -301,23 +300,23 @@ onUnmounted(() => {
<template v-slot:suffix="{item,index}">
<BaseIcon
v-if="!isWordCollect(item)"
class-name="collect"
class="collect"
@click="toggleWordCollect(item)"
title="收藏" icon="ph:star"/>
<BaseIcon
v-else
class-name="fill"
class="fill"
@click="toggleWordCollect(item)"
title="取消收藏" icon="ph:star-fill"/>
<BaseIcon
v-if="!isWordSimple(item)"
class-name="easy"
class="easy"
@click="toggleWordSimple(item)"
title="标记为简单词"
icon="material-symbols:check-circle-outline-rounded"/>
<BaseIcon
v-else
class-name="fill"
class="fill"
@click="toggleWordSimple(item)"
title="取消标记简单词"
icon="material-symbols:check-circle-rounded"/>