wip
This commit is contained in:
@@ -1,313 +0,0 @@
|
||||
<script setup lang="tsx">
|
||||
|
||||
import { nextTick, useSlots } from "vue";
|
||||
import { Sort } from "@/types/types.ts";
|
||||
import MiniDialog from "@/components/dialog/MiniDialog.vue";
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
import { cloneDeep, debounce, reverse, shuffle } from "@/utils";
|
||||
import PopConfirm from "@/components/PopConfirm.vue";
|
||||
import Empty from "@/components/Empty.vue";
|
||||
import Pagination from '@/components/base/Pagination.vue'
|
||||
import Toast from '@/components/base/toast/Toast.ts'
|
||||
import Checkbox from "@/components/base/checkbox/Checkbox.vue";
|
||||
import DeleteIcon from "@/components/icon/DeleteIcon.vue";
|
||||
import Dialog from "@/components/dialog/Dialog.vue";
|
||||
import BaseInput from "@/components/base/BaseInput.vue";
|
||||
import { Host } from "@/config/env.ts";
|
||||
|
||||
let list = defineModel('list')
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
loading?: boolean
|
||||
showToolbar?: boolean
|
||||
showPagination?: boolean
|
||||
exportLoading?: boolean
|
||||
importLoading?: boolean
|
||||
del?: Function
|
||||
batchDel?: Function
|
||||
add?: Function
|
||||
total: number
|
||||
}>(), {
|
||||
loading: true,
|
||||
showToolbar: true,
|
||||
showPagination: true,
|
||||
exportLoading: false,
|
||||
importLoading: false,
|
||||
del: () => void 0,
|
||||
add: () => void 0,
|
||||
batchDel: () => void 0
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
click: [val: {
|
||||
item: any,
|
||||
index: number
|
||||
}],
|
||||
importData: [e: Event]
|
||||
exportData: []
|
||||
}>()
|
||||
|
||||
let listRef: any = $ref()
|
||||
|
||||
function scrollToBottom() {
|
||||
nextTick(() => {
|
||||
listRef?.scrollTo(0, listRef.scrollHeight)
|
||||
})
|
||||
}
|
||||
|
||||
function scrollToTop() {
|
||||
nextTick(() => {
|
||||
listRef?.scrollTo(0, 0)
|
||||
})
|
||||
}
|
||||
|
||||
function scrollToItem(index: number) {
|
||||
nextTick(() => {
|
||||
listRef?.children[index]?.scrollIntoView({ block: 'center', behavior: 'smooth' })
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
let pageNo = $ref(1)
|
||||
let pageSize = $ref(50)
|
||||
let currentList = $computed(() => {
|
||||
if (searchKey) {
|
||||
return list.value.filter(v => v.word.includes(searchKey))
|
||||
}
|
||||
if (!props.showPagination) return list.value
|
||||
return list.value.slice((pageNo - 1) * pageSize, (pageNo - 1) * pageSize + pageSize)
|
||||
})
|
||||
|
||||
let selectIds = $ref([])
|
||||
let selectAll = $computed(() => {
|
||||
return !!selectIds.length
|
||||
})
|
||||
|
||||
function toggleSelect(item) {
|
||||
let rIndex = selectIds.findIndex(v => v === item.id)
|
||||
if (rIndex > -1) {
|
||||
selectIds.splice(rIndex, 1)
|
||||
} else {
|
||||
selectIds.push(item.id)
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSelectAll() {
|
||||
if (selectAll) {
|
||||
selectIds = []
|
||||
} else {
|
||||
selectIds = currentList.map(v => v.id)
|
||||
}
|
||||
}
|
||||
|
||||
let searchKey = $ref('')
|
||||
let showSortDialog = $ref(false)
|
||||
let showSearchInput = $ref(false)
|
||||
let showImportDialog = $ref(false)
|
||||
|
||||
const closeImportDialog = () => showImportDialog = false
|
||||
|
||||
function sort(type: Sort) {
|
||||
if (type === Sort.reverse) {
|
||||
Toast.success('已翻转排序')
|
||||
list.value = reverse(cloneDeep(list.value))
|
||||
}
|
||||
if (type === Sort.random) {
|
||||
Toast.success('已随机排序')
|
||||
list.value = shuffle(cloneDeep(list.value))
|
||||
}
|
||||
showSortDialog = false
|
||||
}
|
||||
|
||||
function handleBatchDel() {
|
||||
props.batchDel(selectIds)
|
||||
selectIds = []
|
||||
}
|
||||
|
||||
function handlePageNo(e) {
|
||||
pageNo = e
|
||||
scrollToTop()
|
||||
}
|
||||
|
||||
const s = useSlots()
|
||||
|
||||
defineExpose({
|
||||
scrollToBottom,
|
||||
scrollToItem,
|
||||
closeImportDialog
|
||||
})
|
||||
defineRender(
|
||||
() => {
|
||||
const d = (item) => <Checkbox
|
||||
modelValue={selectIds.includes(item.id)}
|
||||
onChange={() => toggleSelect(item)}
|
||||
size="large"/>
|
||||
|
||||
return (
|
||||
<div class="flex flex-col gap-3">
|
||||
{
|
||||
props.showToolbar && <div>
|
||||
{
|
||||
showSearchInput ? (
|
||||
<div class="flex gap-4">
|
||||
<BaseInput
|
||||
clearable
|
||||
modelValue={searchKey}
|
||||
onUpdate:modelValue={debounce(e => searchKey = e)}
|
||||
class="flex-1"
|
||||
autofocus>
|
||||
{{
|
||||
subfix: () => <IconFluentSearch24Regular
|
||||
class="text-lg text-gray"
|
||||
/>
|
||||
}}
|
||||
</BaseInput>
|
||||
<BaseButton onClick={() => (showSearchInput = false, searchKey = '')}>取消</BaseButton>
|
||||
</div>
|
||||
) : (
|
||||
<div class="flex justify-between">
|
||||
<div class="flex gap-2 items-center">
|
||||
<Checkbox
|
||||
disabled={!currentList.length}
|
||||
onChange={() => toggleSelectAll()}
|
||||
modelValue={selectAll}
|
||||
size="large"/>
|
||||
<span>{selectIds.length} / {list.value.length}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 relative">
|
||||
{
|
||||
selectIds.length ?
|
||||
<PopConfirm title="确认删除所有选中数据?"
|
||||
onConfirm={handleBatchDel}
|
||||
>
|
||||
<BaseIcon
|
||||
class="del"
|
||||
title="删除">
|
||||
<DeleteIcon/>
|
||||
</BaseIcon>
|
||||
</PopConfirm>
|
||||
: null
|
||||
}
|
||||
<BaseIcon
|
||||
onClick={() => showImportDialog = true}
|
||||
title="导入">
|
||||
<IconSystemUiconsImport/>
|
||||
</BaseIcon>
|
||||
<BaseIcon
|
||||
onClick={() => emit('exportData')}
|
||||
title="导出">
|
||||
{props.exportLoading ? <IconEosIconsLoading/> : <IconPhExportLight/>}
|
||||
</BaseIcon>
|
||||
<BaseIcon
|
||||
onClick={props.add}
|
||||
title="添加单词">
|
||||
<IconFluentAdd20Regular/>
|
||||
</BaseIcon>
|
||||
<BaseIcon
|
||||
disabled={!currentList.length}
|
||||
title="改变顺序"
|
||||
onClick={() => showSortDialog = !showSortDialog}
|
||||
>
|
||||
<IconFluentArrowSort20Regular/>
|
||||
</BaseIcon>
|
||||
<BaseIcon
|
||||
disabled={!currentList.length}
|
||||
onClick={() => showSearchInput = !showSearchInput}
|
||||
title="搜索">
|
||||
<IconFluentSearch20Regular/>
|
||||
</BaseIcon>
|
||||
<MiniDialog
|
||||
modelValue={showSortDialog}
|
||||
onUpdate:modelValue={e => showSortDialog = e}
|
||||
style="width: 8rem;"
|
||||
>
|
||||
<div class="mini-row-title">
|
||||
列表顺序设置
|
||||
</div>
|
||||
<div class="mini-row">
|
||||
<BaseButton size="small" onClick={() => sort(Sort.reverse)}>翻转
|
||||
</BaseButton>
|
||||
<BaseButton size="small" onClick={() => sort(Sort.random)}>随机</BaseButton>
|
||||
</div>
|
||||
</MiniDialog>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
}
|
||||
{
|
||||
props.loading ?
|
||||
<div class="h-full w-full center text-4xl">
|
||||
<IconEosIconsLoading color="gray"/>
|
||||
</div>
|
||||
: currentList.length ? (
|
||||
<>
|
||||
<div class="flex-1 overflow-auto"
|
||||
ref={e => listRef = e}>
|
||||
{currentList.map((item, index) => {
|
||||
return (
|
||||
<div class="list-item-wrapper"
|
||||
key={item.word}
|
||||
>
|
||||
{s.default({ checkbox: d, item, index: (pageSize * (pageNo - 1)) + index + 1 })}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
{
|
||||
props.showPagination && <div class="flex justify-end">
|
||||
<Pagination
|
||||
currentPage={pageNo}
|
||||
onUpdate:current-page={handlePageNo}
|
||||
pageSize={pageSize}
|
||||
onUpdate:page-size={(e) => pageSize = e}
|
||||
pageSizes={[20, 50, 100, 200]}
|
||||
layout="prev, pager, next"
|
||||
total={props.total}/>
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
) : <Empty/>
|
||||
}
|
||||
|
||||
<Dialog modelValue={showImportDialog}
|
||||
onUpdate:modelValue={closeImportDialog}
|
||||
title="导入教程"
|
||||
>
|
||||
<div className="w-100 p-4 pt-0">
|
||||
<div>请按照模板的格式来填写数据</div>
|
||||
<div class="color-red">单词项为必填,其他项可不填</div>
|
||||
<div>翻译:一行一个翻译,前面词性,后面内容(如n.取消);多个翻译请换行</div>
|
||||
<div>例句:一行原文,一行译文;多个请换<span class="color-red">两</span>行</div>
|
||||
<div>短语:一行原文,一行译文;多个请换<span class="color-red">两</span>行</div>
|
||||
<div>同义词、同根词、词源:请前往官方词典,然后编辑其中某个单词,参考其格式</div>
|
||||
<div class="mt-6">
|
||||
模板下载地址:<a href={`https://${Host}/libs/单词导入模板.xlsx`}>单词导入模板</a>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<BaseButton
|
||||
onClick={() => {
|
||||
let d: HTMLDivElement = document.querySelector('#upload-trigger')
|
||||
d.click()
|
||||
}}
|
||||
loading={props.importLoading}>导入</BaseButton>
|
||||
<input
|
||||
id="upload-trigger"
|
||||
type="file"
|
||||
accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
|
||||
onChange={e => emit('importData', e)}
|
||||
class="w-0 h-0 opacity-0"/>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
@@ -1,44 +1,60 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import {Word} from "@/types/types.ts";
|
||||
import { Word } from "@/types/types.ts";
|
||||
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
|
||||
import {usePlayWordAudio} from "@/hooks/sound.ts";
|
||||
import { usePlayWordAudio } from "@/hooks/sound.ts";
|
||||
import Tooltip from "@/components/base/Tooltip.vue";
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
import { useWordOptions } from "@/hooks/dict.ts";
|
||||
|
||||
withDefaults(defineProps<{
|
||||
item: Word,
|
||||
showTranslate?: boolean
|
||||
showWord?: boolean
|
||||
showTransPop?: boolean
|
||||
hiddenOptionIcon?: boolean
|
||||
showOption?: boolean
|
||||
showCollectIcon?: boolean
|
||||
showMarkIcon?: boolean
|
||||
index?: number
|
||||
active?: boolean
|
||||
}>(), {
|
||||
showTranslate: true,
|
||||
showWord: true,
|
||||
showTransPop: true,
|
||||
hiddenOptionIcon: false,
|
||||
showOption: true,
|
||||
showCollectIcon: true,
|
||||
showMarkIcon: true,
|
||||
active: false,
|
||||
})
|
||||
|
||||
const playWordAudio = usePlayWordAudio()
|
||||
|
||||
const {
|
||||
isWordCollect,
|
||||
toggleWordCollect,
|
||||
isWordSimple,
|
||||
toggleWordSimple
|
||||
} = useWordOptions()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="common-list-item"
|
||||
:class="{hiddenOptionIcon}"
|
||||
:class="{active}"
|
||||
>
|
||||
<div class="left">
|
||||
<slot name="prefix" :item="item"></slot>
|
||||
<div class="title-wrapper">
|
||||
<div class="item-title">
|
||||
<span class="text-sm translate-y-0.7 text-gray-500" v-if="index != undefined">{{ index }}.</span>
|
||||
<span class="word" :class="!showWord && 'word-shadow'">{{ item.word }}</span>
|
||||
<span class="phonetic">{{ item.phonetic0 }}</span>
|
||||
<span class="phonetic text-gray">{{ item.phonetic0 }}</span>
|
||||
<VolumeIcon class="volume" @click="playWordAudio(item.word)"></VolumeIcon>
|
||||
</div>
|
||||
<div class="item-sub-title flex flex-col gap-2" v-if="item.trans.length && showTranslate">
|
||||
<div v-for="v in item.trans">
|
||||
<Tooltip
|
||||
v-if="v.cn.length > 30 && showTransPop"
|
||||
:title="v.pos + ' ' + v.cn"
|
||||
v-if="v.cn.length > 30 && showTransPop"
|
||||
:title="v.pos + ' ' + v.cn"
|
||||
>
|
||||
<span>{{ v.pos + ' ' + v.cn.slice(0, 30) + '...' }}</span>
|
||||
</Tooltip>
|
||||
@@ -47,13 +63,29 @@ const playWordAudio = usePlayWordAudio()
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="right" v-if="showOption">
|
||||
<slot name="suffix" :item="item"></slot>
|
||||
<BaseIcon
|
||||
v-if="showCollectIcon"
|
||||
:class="!isWordCollect(item)?'collect':'fill'"
|
||||
@click.stop="toggleWordCollect(item)"
|
||||
:title="!isWordCollect(item) ? '收藏' : '取消收藏'">
|
||||
<IconFluentStar16Regular v-if="!isWordCollect(item)"/>
|
||||
<IconFluentStar16Filled v-else/>
|
||||
</BaseIcon>
|
||||
|
||||
<BaseIcon
|
||||
v-if="showMarkIcon"
|
||||
:class="!isWordSimple(item)?'collect':'fill'"
|
||||
@click.stop="toggleWordSimple(item)"
|
||||
:title="!isWordSimple(item) ? '标记为已掌握' : '取消标记已掌握'">
|
||||
<IconFluentCheckmarkCircle16Regular v-if="!isWordSimple(item)"/>
|
||||
<IconFluentCheckmarkCircle16Filled v-else/>
|
||||
</BaseIcon>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import { Article } from "@/types/types.ts";
|
||||
import BaseList from "@/components/list/BaseList.vue";
|
||||
import BaseInput from "@/components/base/BaseInput.vue";
|
||||
import { Article } from '@/types/types.ts'
|
||||
import BaseList from '@/components/list/BaseList.vue'
|
||||
import BaseInput from '@/components/base/BaseInput.vue'
|
||||
import { useArticleOptions } from '@/hooks/dict.ts'
|
||||
import BaseIcon from '@/components/BaseIcon.vue'
|
||||
|
||||
interface IProps {
|
||||
list: Article[];
|
||||
showTranslate?: boolean;
|
||||
list: Article[]
|
||||
showTranslate?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<IProps>(), {
|
||||
@@ -15,8 +16,8 @@ const props = withDefaults(defineProps<IProps>(), {
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
click: [val: { item: Article, index: number }],
|
||||
title: [val: { item: Article, index: number }],
|
||||
click: [val: { item: Article; index: number }]
|
||||
title: [val: { item: Article; index: number }]
|
||||
}>()
|
||||
|
||||
let searchKey = $ref('')
|
||||
@@ -24,10 +25,13 @@ let localList = $computed(() => {
|
||||
if (searchKey) {
|
||||
//把搜索内容,分词之后,判断是否有这个词,比单纯遍历包含体验更好
|
||||
let t = searchKey.toLowerCase()
|
||||
let strings = t.split(' ').filter(v => v);
|
||||
let strings = t.split(' ').filter(v => v)
|
||||
let res = props.list.filter((item: Article) => {
|
||||
return strings.some(value => {
|
||||
return item.title.toLowerCase().includes(value) || item.titleTranslate.toLowerCase().includes(value)
|
||||
return (
|
||||
item.title.toLowerCase().includes(value) ||
|
||||
item.titleTranslate.toLowerCase().includes(value)
|
||||
)
|
||||
})
|
||||
})
|
||||
try {
|
||||
@@ -38,16 +42,15 @@ let localList = $computed(() => {
|
||||
res.push(props.list[d - 1])
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
}
|
||||
} catch (err) {}
|
||||
return res.sort((a: Article, b: Article) => {
|
||||
//使完整包含的条目更靠前
|
||||
const aMatch = a.title.toLowerCase().includes(t);
|
||||
const bMatch = b.title.toLowerCase().includes(t);
|
||||
const aMatch = a.title.toLowerCase().includes(t)
|
||||
const bMatch = b.title.toLowerCase().includes(t)
|
||||
|
||||
if (aMatch && !bMatch) return -1; // a 靠前
|
||||
if (!aMatch && bMatch) return 1; // b 靠前
|
||||
return 0; // 都匹配或都不匹配,保持原顺序
|
||||
if (aMatch && !bMatch) return -1 // a 靠前
|
||||
if (!aMatch && bMatch) return 1 // b 靠前
|
||||
return 0 // 都匹配或都不匹配,保持原顺序
|
||||
})
|
||||
} else {
|
||||
return props.list
|
||||
@@ -63,9 +66,9 @@ function scrollToBottom() {
|
||||
function scrollToItem(index: number) {
|
||||
listRef?.scrollToItem(index)
|
||||
}
|
||||
const { isArticleCollect, toggleArticleCollect } = useArticleOptions()
|
||||
|
||||
defineExpose({ scrollToBottom, scrollToItem })
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -77,23 +80,38 @@ defineExpose({ scrollToBottom, scrollToItem })
|
||||
</template>
|
||||
</BaseInput>
|
||||
</div>
|
||||
<BaseList ref="listRef"
|
||||
@click="(e: any) => emit('click', e)"
|
||||
:list="localList"
|
||||
v-bind="$attrs">
|
||||
<template v-slot:prefix="{ item, index }">
|
||||
<slot name="prefix" :item="item" :index="index"></slot>
|
||||
</template>
|
||||
<template v-slot="{ item, index }">
|
||||
<div class="item-title">
|
||||
<div class="name"> {{ `${searchKey ? '' : (index + 1) + '. '}${item.title}` }}</div>
|
||||
<BaseList ref="listRef" @click="(e: any) => emit('click', e)" :list="localList" v-bind="$attrs">
|
||||
<template v-slot="{ item, index, active }">
|
||||
<div class="common-list-item" :class="{ active }">
|
||||
<div class="left">
|
||||
<div class="title-wrapper">
|
||||
<div class="item-title">
|
||||
<div class="name">
|
||||
<span class="text-sm text-gray-500" v-if="index != undefined && !searchKey">
|
||||
{{ index }}.
|
||||
</span>
|
||||
{{ 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">
|
||||
<BaseIcon
|
||||
:class="!isArticleCollect(item) ? 'collect' : 'fill'"
|
||||
@click.stop="toggleArticleCollect(item)"
|
||||
:title="!isArticleCollect(item) ? '收藏' : '取消收藏'"
|
||||
>
|
||||
<IconFluentStar16Regular v-if="!isArticleCollect(item)" />
|
||||
<IconFluentStar16Filled v-else />
|
||||
</BaseIcon>
|
||||
<BaseIcon title="可播放音频" v-if="item.audioSrc || item.audioFileId">
|
||||
<IconBxVolumeFull class="opacity-100! color-gray" />
|
||||
</BaseIcon>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-sub-title" v-if="item.titleTranslate && showTranslate">
|
||||
<div class="item-translate"> {{ ` ${item.titleTranslate}` }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:suffix="{ item, index }">
|
||||
<slot name="suffix" :item="item" :index="index"></slot>
|
||||
</template>
|
||||
</BaseList>
|
||||
</div>
|
||||
|
||||
@@ -1,26 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
import { useSettingStore } from "@/stores/setting.ts";
|
||||
import { useSettingStore } from '@/stores/setting.ts'
|
||||
import { nextTick, watch } from 'vue'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
list?: any[],
|
||||
activeIndex?: number,
|
||||
activeId?: number | string,
|
||||
isActive?: boolean
|
||||
static?: boolean
|
||||
}>(), {
|
||||
list: [],
|
||||
activeIndex: -1,
|
||||
activeId: '',
|
||||
isActive: false,
|
||||
static: true
|
||||
})
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
list?: any[]
|
||||
activeIndex?: number
|
||||
activeId?: number | string
|
||||
isActive?: boolean
|
||||
static?: boolean
|
||||
}>(),
|
||||
{
|
||||
list: [],
|
||||
activeIndex: -1,
|
||||
activeId: '',
|
||||
isActive: false,
|
||||
static: true,
|
||||
}
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
click: [val: {
|
||||
item: any,
|
||||
index: number
|
||||
}],
|
||||
click: [
|
||||
val: {
|
||||
item: any
|
||||
index: number
|
||||
},
|
||||
]
|
||||
}>()
|
||||
|
||||
//虚拟列表长度限制
|
||||
@@ -41,35 +46,45 @@ function scrollViewToCenter(index: number) {
|
||||
if (props.list.length > limit) {
|
||||
listRef?.scrollToItem(index)
|
||||
} else {
|
||||
listRef?.children[index]?.scrollIntoView({block: 'center', behavior: 'smooth'})
|
||||
listRef?.children[index]?.scrollIntoView({ block: 'center', behavior: 'smooth' })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
watch(() => localActiveIndex, (n: any) => {
|
||||
if (props.static) return
|
||||
if (settingStore.showPanel) {
|
||||
scrollViewToCenter(n)
|
||||
}
|
||||
}, {immediate: true})
|
||||
|
||||
watch(() => props.isActive, (n: boolean) => {
|
||||
if (props.static) return
|
||||
if (n) {
|
||||
setTimeout(() => scrollViewToCenter(localActiveIndex), 300)
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => props.list, () => {
|
||||
if (props.static) return
|
||||
nextTick(() => {
|
||||
if (props.list.length > limit) {
|
||||
listRef?.scrollToItem(0)
|
||||
} else {
|
||||
listRef?.scrollTo(0, 0)
|
||||
watch(
|
||||
() => localActiveIndex,
|
||||
(n: any) => {
|
||||
if (props.static) return
|
||||
if (settingStore.showPanel) {
|
||||
scrollViewToCenter(n)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.isActive,
|
||||
(n: boolean) => {
|
||||
if (props.static) return
|
||||
if (n) {
|
||||
setTimeout(() => scrollViewToCenter(localActiveIndex), 300)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.list,
|
||||
() => {
|
||||
if (props.static) return
|
||||
nextTick(() => {
|
||||
if (props.list.length > limit) {
|
||||
listRef?.scrollToItem(0)
|
||||
} else {
|
||||
listRef?.scrollTo(0, 0)
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
function scrollToBottom() {
|
||||
nextTick(() => {
|
||||
@@ -86,91 +101,51 @@ function scrollToItem(index: number) {
|
||||
if (props.list.length > limit) {
|
||||
listRef?.scrollToItem(index)
|
||||
} else {
|
||||
listRef?.children[index]?.scrollIntoView({block: 'center', behavior: 'smooth'})
|
||||
listRef?.children[index]?.scrollIntoView({ block: 'center', behavior: 'smooth' })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function itemIsActive(item: any, index: number) {
|
||||
return props.activeId ?
|
||||
props.activeId == item.id
|
||||
: props.activeIndex === index
|
||||
return props.activeId ? props.activeId == item.id : props.activeIndex === index
|
||||
}
|
||||
|
||||
defineExpose({scrollToBottom, scrollToItem})
|
||||
|
||||
defineExpose({ scrollToBottom, scrollToItem })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DynamicScroller
|
||||
v-if="list.length>limit"
|
||||
:items="list"
|
||||
ref="listRef"
|
||||
:min-item-size="90"
|
||||
class="scroller"
|
||||
v-if="list.length > limit"
|
||||
: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"
|
||||
:item="item"
|
||||
:active="active"
|
||||
:size-dependencies="[item.id]"
|
||||
:data-index="index"
|
||||
>
|
||||
<div class="list-item-wrapper">
|
||||
<div class="common-list-item"
|
||||
:class="{
|
||||
active:itemIsActive(item,index),
|
||||
}"
|
||||
@click="emit('click',{item,index})"
|
||||
>
|
||||
<div class="left">
|
||||
<slot name="prefix" :item="item" :index="index"></slot>
|
||||
<div class="title-wrapper">
|
||||
<slot :item="item" :index="index"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<slot name="suffix" :item="item" :index="index"></slot>
|
||||
</div>
|
||||
<div class="list-item-wrapper" v-for="(item, index) in props.list" :key="item.title">
|
||||
<div @click="emit('click', { item, index })">
|
||||
<slot :item="item" :index="index" :active="itemIsActive(item, index)"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</DynamicScrollerItem>
|
||||
</template>
|
||||
</DynamicScroller>
|
||||
<div
|
||||
v-else
|
||||
class="scroller"
|
||||
style="overflow: auto;"
|
||||
ref="listRef">
|
||||
<div class="list-item-wrapper"
|
||||
v-for="(item,index) in props.list"
|
||||
:key="item.title"
|
||||
>
|
||||
<div class="common-list-item"
|
||||
:class="{
|
||||
active:itemIsActive(item,index),
|
||||
}"
|
||||
@click="emit('click',{item,index})"
|
||||
>
|
||||
<div class="left">
|
||||
<slot name="prefix" :item="item" :index="index"></slot>
|
||||
<div class="title-wrapper">
|
||||
<slot :item="item" :index="index"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<slot name="suffix" :item="item" :index="index"></slot>
|
||||
</div>
|
||||
<div v-else class="scroller" style="overflow: auto" ref="listRef">
|
||||
<div class="list-item-wrapper" v-for="(item, index) in props.list" :key="item.title">
|
||||
<div @click="emit('click', { item, index })">
|
||||
<slot :item="item" :index="index" :active="itemIsActive(item, index)"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
|
||||
.scroller {
|
||||
flex: 1;
|
||||
//padding: 0 var(--space);
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import { Word } from "@/types/types.ts";
|
||||
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
|
||||
import BaseList from "@/components/list/BaseList.vue";
|
||||
import { usePlayWordAudio } from "@/hooks/sound.ts";
|
||||
import Tooltip from "@/components/base/Tooltip.vue";
|
||||
import WordItem from "@/components/WordItem.vue";
|
||||
import { Word } from "@/types/types.ts";
|
||||
import WordItem from "../WordItem.vue";
|
||||
|
||||
withDefaults(defineProps<{
|
||||
list: Word[],
|
||||
@@ -19,7 +16,6 @@ withDefaults(defineProps<{
|
||||
|
||||
const emit = defineEmits<{
|
||||
click: [val: { item: Word, index: number }],
|
||||
title: [val: { item: Word, index: number }],
|
||||
}>()
|
||||
|
||||
const listRef: any = $ref(null as any)
|
||||
@@ -32,22 +28,18 @@ function scrollToItem(index: number) {
|
||||
listRef?.scrollToItem(index)
|
||||
}
|
||||
|
||||
const playWordAudio = usePlayWordAudio()
|
||||
|
||||
defineExpose({ scrollToBottom, scrollToItem })
|
||||
defineExpose({scrollToBottom, scrollToItem})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BaseList ref="listRef" @click="(e: any) => emit('click', e)" :list="list" v-bind="$attrs">
|
||||
<template v-slot:prefix="{ item, index }">
|
||||
<slot name="prefix" :item="item" :index="index"></slot>
|
||||
</template>
|
||||
<template v-slot="{ item, index }">
|
||||
<WordItem :item="item"/>
|
||||
</template>
|
||||
<template v-slot:suffix="{ item, index }">
|
||||
<slot name="suffix" :item="item" :index="index"></slot>
|
||||
</template>
|
||||
<BaseList
|
||||
ref="listRef"
|
||||
@click="(e:any) => emit('click',e)"
|
||||
:list="list"
|
||||
v-bind="$attrs">
|
||||
<template v-slot="{ item, index, active }">
|
||||
<WordItem :item="item" :index="index" :active="active" />
|
||||
</template>
|
||||
</BaseList>
|
||||
</template>
|
||||
Reference in New Issue
Block a user